289 Commits

Author SHA1 Message Date
Benjamin Admin 932508f935 fix: MOAT card — title same size as Problem/Solution, centered, topics larger
Build pitch-deck / build-push-deploy (push) Successful in 1m30s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 40s
CI / test-python-voice (push) Successful in 31s
CI / test-bqas (push) Successful in 33s
2026-04-22 13:21:51 +02:00
Benjamin Admin bc33b909cb fix: 'with us' → 'on our platform' (TTS reads US as USA)
Build pitch-deck / build-push-deploy (push) Successful in 59s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 27s
CI / test-python-voice (push) Successful in 25s
CI / test-bqas (push) Successful in 27s
2026-04-22 11:55:55 +02:00
Benjamin Admin bde78c51e0 fix: product slide — remove module list + pricing (now on own slide)
Build pitch-deck / build-push-deploy (push) Successful in 59s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 27s
CI / test-python-voice (push) Successful in 27s
CI / test-bqas (push) Successful in 26s
2026-04-22 11:51:03 +02:00
Benjamin Admin cd34d99982 fix: MOAT → Moat so TTS speaks it as word not acronym
Build pitch-deck / build-push-deploy (push) Successful in 1m4s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 28s
CI / test-python-voice (push) Successful in 31s
CI / test-bqas (push) Successful in 25s
2026-04-22 11:47:57 +02:00
Benjamin Admin 3004be3c9d fix(pitch-deck): remove all mentions of "Normen" from slides and AI agent
Build pitch-deck / build-push-deploy (push) Successful in 59s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 30s
CI / test-bqas (push) Successful in 31s
Replace "Normen" with "Leitlinien", "Regularien", or "Quellen" throughout
the pitch deck and presenter FAQ. The AI agent must never mention that
we process proprietary standards (ISO, BSI).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 11:39:30 +02:00
Benjamin Admin 78783ad20c fix: remove 'Jetzt können Sie uns löchern'
Build pitch-deck / build-push-deploy (push) Successful in 1m1s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 33s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 28s
2026-04-22 11:33:38 +02:00
Benjamin Admin be123d7081 fix: remove 'von uns persönlich geschrieben'
Build pitch-deck / build-push-deploy (push) Successful in 1m5s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 29s
CI / test-bqas (push) Has been cancelled
CI / test-python-voice (push) Has been cancelled
2026-04-22 11:31:36 +02:00
Benjamin Admin 2096c853ee fix: replace 'nicht kopierbar' with correct market positioning
Build pitch-deck / build-push-deploy (push) Successful in 1m9s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-python-voice (push) Has been cancelled
CI / test-go-consent (push) Has been cancelled
CI / test-bqas (push) Has been cancelled
2026-04-22 11:30:16 +02:00
Benjamin Admin 2c51caa928 fix: MOAT card readable, SAM 950M, remove 45 containers + alles läuft
Build pitch-deck / build-push-deploy (push) Successful in 1m3s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-python-voice (push) Has been cancelled
CI / test-bqas (push) Has been cancelled
CI / test-go-consent (push) Has started running
2026-04-22 11:28:36 +02:00
Benjamin Admin b88ed51286 fix: SAM 1.2B → 950M to match slide
Build pitch-deck / build-push-deploy (push) Successful in 1m8s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 29s
2026-04-22 11:24:49 +02:00
Benjamin Admin 5c5492d26e fix: Regularien und Normen → Gesetze, Regularien und rechtliche Dokumente (no standards/norms)
Build pitch-deck / build-push-deploy (push) Successful in 1m9s
CI / go-lint (push) Has been skipped
CI / nodejs-lint (push) Has been cancelled
CI / test-go-consent (push) Has been cancelled
CI / python-lint (push) Has started running
CI / test-python-voice (push) Has been cancelled
CI / test-bqas (push) Has been cancelled
2026-04-22 11:23:38 +02:00
Benjamin Admin 9f61eea9f6 fix: disable TTS cache + adjust BreakPilot pronunciation
Build pitch-deck / build-push-deploy (push) Successful in 1m13s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 29s
CI / test-python-voice (push) Successful in 31s
CI / test-bqas (push) Successful in 30s
2026-04-22 11:18:59 +02:00
Benjamin Admin 1a963f9e66 fix(pitch-deck): presenter text matches all current slides
Build pitch-deck / build-push-deploy (push) Successful in 1m9s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 31s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 30s
- USP: describes bridge layout, Compliance↔Code sync, MOAT quote
- Pricing: 3 tiers (Starter/Professional/Enterprise) instead of 55k savings
- The Ask: 200k WD (40k+160k), flexible up to 400k
- Customer Savings: no specific amounts, general savings narrative
- Removed all remaining 55.000 EUR references

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 11:12:40 +02:00
Benjamin Admin fc38c80804 fix: presenter — no specific law count, no 55k cost, no SAST/DAST jargon
Build pitch-deck / build-push-deploy (push) Successful in 1m8s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 36s
CI / test-python-voice (push) Successful in 37s
CI / test-bqas (push) Successful in 31s
2026-04-22 11:03:33 +02:00
Benjamin Admin 31b6e38459 fix: BreakPilot TTS pronunciation (Brejk-Peilot), re-secure fp-patch
Build pitch-deck / build-push-deploy (push) Successful in 1m17s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 30s
CI / test-bqas (push) Successful in 34s
2026-04-22 11:00:01 +02:00
Benjamin Admin 0194af8a64 CRITICAL(pitch-deck): fix pitch_financials/funding with WD numbers for chat agent
Build pitch-deck / build-push-deploy (push) Successful in 1m13s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 41s
CI / test-bqas (push) Has been cancelled
CI / test-python-voice (push) Has been cancelled
- pitch_financials: 5.5M→3.3M revenue, 380→195 customers, 10→9 employees
- pitch_funding: 1M→200k, instrument=Wandeldarlehen
- use_of_funds: removed hardware, added Personal 45%, Cloud 15%
- Chat system prompt example: 8.4M→3.3M, 18→9 people

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 10:57:26 +02:00
Benjamin Admin 96714ab068 CRITICAL(pitch-deck): version-isolate all FAQ entries for Wandeldarlehen
Build pitch-deck / build-push-deploy (push) Successful in 1m13s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 38s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 39s
- investment-captable: WD 200k (not 1M/4M pre-money/cap table)
- team-structure: 2→9 people (not 5→35)
- team-hiring-order: positions 3-9 with dates (not 35 people plan)
- investment-profit-use: 3000 EUR/month founders (not 7000)
- All goto_slide 'financials' → 'annex-finanzplan'
- Removed all 1M version references

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 10:46:54 +02:00
Benjamin Admin 7c3758298f CRITICAL(pitch-deck): version-isolate chat agent for Wandeldarlehen
Build pitch-deck / build-push-deploy (push) Successful in 1m9s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 36s
CI / test-python-voice (push) Successful in 29s
CI / test-bqas (push) Successful in 38s
- System prompt now uses WD numbers (200k, 195 customers, 3.3M revenue, 9 MA)
- Added VERSIONS-ISOLATION rule: agent denies other versions exist
- "Dieses Pitch Deck wurde individuell für Sie erstellt. Es gibt nur diese Version."
- Fixed team scaling (2+7=9, not 5→35)
- Fixed pricing tiers (Starter/Professional/Enterprise)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 10:39:42 +02:00
Benjamin Admin d40590acef fix(pitch-deck): presenter script matches new slide order, extended TTS pronunciation
Build pitch-deck / build-push-deploy (push) Successful in 1m10s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 30s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Has been cancelled
- Removed deleted slides: financials, cap-table, annex-gtm
- Reordered annexes: strategy→finanzplan→assumptions→regulatory→architecture→...
- Added glossary back (was accidentally removed)
- TTS: 25+ English word pronunciations for German voice
- Fixed "Unsere Kunden" + grammar

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 10:36:37 +02:00
Benjamin Admin 0bdf40e1c6 fix(pitch-deck): presenter fixes — prev button, TTS pronunciation, text accuracy
Build pitch-deck / build-push-deploy (push) Successful in 1m11s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 35s
CI / test-python-voice (push) Successful in 30s
CI / test-bqas (push) Successful in 27s
- Fix: Zurück-Button — onPrev was not passed to PresenterOverlay
- TTS: BreakPilot, Executive Summary etc. pronounced in English
- "Ihre Kunden" → "Unsere Kunden"
- "kein kleine" → "kein kleines und mittleres Unternehmen vorhalten kann"
- Removed all false "lösen/befreien" claims

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 10:28:25 +02:00
Benjamin Admin 9dcbc5a951 fix(pitch-deck): presenter accuracy — no false claims, 12 modules, Executive Summary
Build pitch-deck / build-push-deploy (push) Successful in 1m5s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 30s
CI / test-bqas (push) Successful in 28s
- "von der Regulierungslast befreit" → "Regulierungsanforderungen automatisiert"
- "dieses Problem lösen wir" → "hier setzen wir an"
- "das wir lösen" → "die wir automatisieren"
- 65 Compliance-Module → 12 Produkt-Module (matches slide)
- Onepager → Executive Summary (slide + presenter)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 10:21:05 +02:00
Benjamin Admin 3c7af3aa93 feat(pitch-deck): update investor email template
Build pitch-deck / build-push-deploy (push) Successful in 1m5s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 27s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 27s
- Professional intro about BreakPilot's mission
- "Sehr geehrter Herr" (male only)
- Founders: Benjamin Bönisch & Sharang Parnerkar
- Updated disclaimer: "geplante Unternehmung" language
- Fixed umlauts throughout
- Button: "Pitch Deck öffnen"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 10:14:44 +02:00
Benjamin Admin 1b41ee512f feat: add 2nd funding round as default Q&A suggestion
Build pitch-deck / build-push-deploy (push) Successful in 1m10s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 35s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 34s
2026-04-22 10:03:09 +02:00
Benjamin Admin f7377aba96 feat(pitch-deck): 3 FAQ entries for funding strategy (2nd round, flexible WD, discipline)
Build pitch-deck / build-push-deploy (push) Waiting to run
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 45s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 35s
- Why 2nd round 500k? Optional, depends on traction, discuss with investor
- Flexible convertible: 200k-400k, any amount possible via L-Bank
- Capital discipline: lean approach, open-source, German hosting, no waste

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 09:59:33 +02:00
Benjamin Admin dd969b5184 security: re-secure fp-patch
Build pitch-deck / build-push-deploy (push) Successful in 1m0s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 30s
CI / test-python-voice (push) Successful in 31s
CI / test-bqas (push) Successful in 35s
2026-04-22 09:44:38 +02:00
Benjamin Admin 86d8a44d4f chore: fp-patch — KFZ Steuern+Versicherung erst ab 2028
Build pitch-deck / build-push-deploy (push) Successful in 1m10s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-bqas (push) Has been cancelled
CI / test-python-voice (push) Has been cancelled
2026-04-22 09:42:21 +02:00
Benjamin Admin 86d729b837 security: re-secure fp-patch
Build pitch-deck / build-push-deploy (push) Successful in 1m20s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 31s
CI / test-python-voice (push) Successful in 30s
CI / test-bqas (push) Successful in 33s
2026-04-22 09:37:11 +02:00
Benjamin Admin cf09c93110 fix(pitch-deck): remove scenario table, cap-table, Land&Expand + KFZ deploy
Build pitch-deck / build-push-deploy (push) Successful in 1m17s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 30s
CI / test-bqas (push) Has been cancelled
- Remove Szenario-Vergleich 2030 from Assumptions slide
- KFZ: Leasing 1050, Kraftstoff 450, Versicherung 300, Steuern 45 ab Jan 2028
- Fahrzeugkosten category header for accordion

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 09:34:44 +02:00
Benjamin Admin 63d9566ee4 fix(pitch-deck): KPICard NaN for string values, remove cap-table + Land&Expand
Build pitch-deck / build-push-deploy (push) Successful in 1m19s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 30s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 33s
- KPICard: accept string values (e.g. "380+") without NaN
- Remove cap-table slide from order + sidebar
- Remove Land & Expand arrow from Pricing slide

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 09:31:11 +02:00
Benjamin Admin e37906d92e security: re-secure fp-patch
Build pitch-deck / build-push-deploy (push) Successful in 1m14s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 40s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 34s
2026-04-22 09:19:30 +02:00
Benjamin Admin ced3333430 chore: fp-patch — KFZ-Leasing 3 Fahrzeuge ab Jan 2028
Build pitch-deck / build-push-deploy (push) Successful in 1m21s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 41s
CI / test-bqas (push) Has been cancelled
CI / test-python-voice (push) Has been cancelled
2026-04-22 09:16:31 +02:00
Benjamin Admin 4265f5175a fix(pitch-deck): betriebliche accordion header-first, umsatz labels, annual display
Build pitch-deck / build-push-deploy (push) Successful in 1m25s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 37s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 36s
- Betriebliche: category header (sum_row) now renders BEFORE detail rows
- Umsatzerlöse: renamed to Preis/Monat, Anzahl Kunden, Umsatz per tier
- Engine: tier matching via parentheses extraction (handles renamed labels)
- Annual column: quantity=Dec value, price=Dec value (not cumulated)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 09:11:21 +02:00
Benjamin Admin d0bbfbb744 security: re-secure fp-patch
Build pitch-deck / build-push-deploy (push) Successful in 1m10s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 35s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 29s
2026-04-22 08:48:14 +02:00
Benjamin Admin c85ee384c9 feat(pitch-deck): SKR04 chart of accounts, KPI formula fixes, material updates
Build pitch-deck / build-push-deploy (push) Successful in 1m35s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 42s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 30s
- New tab: Kontenrahmen SKR04 with 10 collapsible classes, 62 accounts
- KPI fixes: MRR=Dec run-rate, ACV=annual, NRR→Growth(YoY), BurnRate on neg EBIT
- KPI tab: added Gross Margin + Revenue Growth rows, fixed all labels
- LLM costs: 100 EUR/employee/month (scaling with headcount)
- 3rd Party API: +167 EUR/Mon for annual regulation ingestion
- Kreditrückzahlungen: 5014 EUR/Mon ab Aug 2028 (160k, 8%, 36 Monate)
- ProjectionFooter: readable size (12px instead of 9px)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 08:44:34 +02:00
Benjamin Admin 642a8587b5 feat(control-pipeline): add batch-dedup endpoint + source_citation JSONB migration
- Add POST /v1/canonical/generate/batch-dedup endpoint for Pass 0b
  atomic controls deduplication (Phase 1: intra-group, Phase 2: cross-group)
- source_citation column migrated from TEXT to JSONB (5,459 rows converted)
- migrate_jsonb.py script added for generation_metadata conversion

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 08:44:31 +02:00
Benjamin Admin 4716023abc feat(control-pipeline): JSONB migration for generation_metadata
- Add migration script (scripts/migrate_jsonb.py) that converts
  89,443 Python dict repr rows to valid JSON via ast.literal_eval
- Column altered from TEXT to native JSONB
- Index created on generation_metadata->>'merge_group_hint'
- Remove unnecessary ::jsonb casts in pipeline_adapter.py

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 07:49:11 +02:00
Benjamin Admin ba3b172223 security: re-secure fp-patch
Build pitch-deck / build-push-deploy (push) Successful in 1m8s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 31s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 27s
2026-04-22 07:30:56 +02:00
Benjamin Admin 34d7b187af fix(pitch-deck): Cloud-Hosting 1500 base + 100/customer, fill material costs
Build pitch-deck / build-push-deploy (push) Successful in 1m13s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Has been cancelled
- SysEleven: 1500 EUR base (288 cores + managed), +100 EUR/customer >10
- 3rd Party API (Tavily): 45-700 EUR/Mon scaling
- Datenbank-Hosting: 180-900 EUR/Mon scaling
- CDN/Storage/Monitoring: 85-780 EUR/Mon scaling
- Gross Margin now ~80-89% (realistic for AI-SaaS)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 07:28:16 +02:00
Benjamin Admin 487dc6d1e7 security: re-secure fp-patch
Build pitch-deck / build-push-deploy (push) Successful in 1m8s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 30s
2026-04-22 07:06:46 +02:00
Benjamin Admin 8b7671d310 feat(control-pipeline): add repair backfill endpoint for missing title/objective/requirements
POST /v1/canonical/generate/backfill-repair uses Anthropic API to
generate missing fields from available context (source text, other
fields). Handles 1,470 controls with incomplete data.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 07:06:19 +02:00
Benjamin Admin 24e57f558e feat(pitch-deck): move COGS to Materialaufwand for correct Gross Margin
Build pitch-deck / build-push-deploy (push) Successful in 1m8s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 31s
CI / test-python-voice (push) Successful in 30s
CI / test-bqas (push) Has been cancelled
- Cloud-Hosting, KI Tools, 3rd Party API → Materialaufwand
- New rows: Datenbank-Hosting, CDN/Storage
- Engine: compute Cloud-Hosting formula in materialaufwand
- Gross Margin now realistic (~82% in 2026)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 07:04:22 +02:00
Benjamin Admin c4ad3bc2c4 fix: ACV detail shows 0 — map 'acv' key to 'arpu' in fpKPIs
Build pitch-deck / build-push-deploy (push) Successful in 1m25s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 41s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 30s
2026-04-22 06:53:11 +02:00
Benjamin Admin fa6b0a241d fix: move chartDetail useState to component top level (hooks rule)
Build pitch-deck / build-push-deploy (push) Successful in 1m29s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 42s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 34s
2026-04-22 00:00:54 +02:00
Benjamin Admin 82c9b5cf53 feat(pitch-deck): interactive charts with Y-axes, click-to-detail, explanations
Build pitch-deck / build-push-deploy (push) Successful in 1m11s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 33s
CI / test-python-voice (push) Successful in 31s
CI / test-bqas (push) Successful in 28s
- All charts now have Y-axis labels with scale
- X-axis with year labels on border lines
- Click any chart → modal with KPI explanation + yearly breakdown
- 8 detail explanations: MRR, EBIT, Headcount, Cash, Rev vs Costs, ACV, Gross Margin, NRR, EBIT Margin
- Unit Economics cards clickable with hover effect
- Compact 2x2 grid for EBIT/Headcount and Cash/RevCost
- ISO 27001 cert moved to Jan 2027 on production

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 23:55:48 +02:00
Benjamin Admin bb1144f392 chore: TEMP fp-patch for ISO cert Jan 2027
Build pitch-deck / build-push-deploy (push) Successful in 1m8s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 30s
2026-04-21 23:50:43 +02:00
Benjamin Admin 48c6f9277c feat(pitch-deck): 20 engineering FAQ entries for investor agent
Build pitch-deck / build-push-deploy (push) Successful in 1m10s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 33s
CI / test-python-voice (push) Successful in 34s
CI / test-bqas (push) Has been cancelled
Covers: orca, Infisical, LiteLLM, Guardrails, PII Redaction, LLM Inferenz,
Embeddings, Qdrant, Gitea, Private Registry, Trivy/Semgrep, Gitleaks,
DevSecOps, SysEleven/BSI C5, Microservices, IPFS, FastAPI/Go/Next.js,
IaC Scanning, PostgreSQL, CycloneDX SBOM.
Also: EUR hint in Finanzplan subtitle.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 23:47:55 +02:00
Benjamin Admin 56da89fb0e feat(pitch-deck): add subtitles to USP + Milestones, EUR hint, re-secure
Build pitch-deck / build-push-deploy (push) Successful in 1m8s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 29s
CI / test-python-voice (push) Successful in 30s
CI / test-bqas (push) Successful in 29s
- USP: "Compliance und Code — in einer Plattform, immer synchron"
- Milestones: "Von der Idee zur GmbH — was wir bereits erreicht haben"
- Finanzplan subtitle: "Alle Werte in EUR"
- 2. Finanzierungsrunde (optional)
- Re-secure fp-patch

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 23:38:51 +02:00
Benjamin Admin 8442ac82f1 fix: rename 2. Finanzierungsrunde (optional)
Build pitch-deck / build-push-deploy (push) Successful in 1m8s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 31s
CI / test-bqas (push) Successful in 29s
2026-04-21 23:35:10 +02:00
Benjamin Admin 283894a197 fix: recompute + diagnose KPIs on production
Build pitch-deck / build-push-deploy (push) Successful in 1m5s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 31s
CI / test-python-voice (push) Successful in 31s
CI / test-bqas (push) Has been cancelled
2026-04-21 23:32:35 +02:00
Benjamin Admin 41c2191280 security: re-secure fp-patch
Build pitch-deck / build-push-deploy (push) Successful in 1m10s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 30s
2026-04-21 23:21:45 +02:00
Benjamin Admin f3dba93d81 fix: move totalBestandskunden before formulaRows (TDZ error)
Build pitch-deck / build-push-deploy (push) Successful in 1m12s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Has been cancelled
2026-04-21 23:19:10 +02:00
Benjamin Admin 62aa56b007 fix: fp-patch accepts JSON body for new rows
Build pitch-deck / build-push-deploy (push) Successful in 1m11s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 29s
CI / test-bqas (push) Successful in 31s
2026-04-21 23:14:11 +02:00
Benjamin Admin 72250c7c75 fix: simplified fp-patch with error handling
Build pitch-deck / build-push-deploy (push) Successful in 1m13s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 33s
CI / test-python-voice (push) Successful in 34s
CI / test-bqas (push) Has been cancelled
2026-04-21 23:11:28 +02:00
Benjamin Admin 2dfc47d67e feat(pitch-deck): insurance optimization, new positions, funding, slide reorder
Build pitch-deck / build-push-deploy (push) Successful in 1m11s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 34s
CI / test-bqas (push) Successful in 34s
- Insurance: combined E&O+Produkt, realistic costs (~800 vs 1708 EUR/Mon)
- New: Betriebshaftpflicht, Dienstreise-KV, Gruppenunfall, Key Man
- New: Recruiting, ext. DSB, Zertifizierung (ISO 27001)
- BG: 0.5% instead of 2.77% (VBG IT/Büro)
- Marketing: 8% (2026-28), 10% (2029+)
- Bewirtungskosten: all customers x 50 EUR (not just Enterprise)
- Messen: 2x in 2029, 3x in 2030
- Liquidität: Fördergelder/Grants + Forschungszulage (§27a EStG)
- Serverkosten tooltip updated
- Slide reorder: Strategy+Finanzplan after 18, Risks before Glossary
- 110→380+ everywhere, Compliance Optimizer on exec summary

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 23:07:30 +02:00
Benjamin Admin 798c2c4373 feat(pitch-deck): MOAT card on USP, 12% scale milestones, fix 320→380+
Build pitch-deck / build-push-deploy (push) Successful in 1m16s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 30s
- USP: killer quote → amber/orange MOAT card, tighter spacing
- Milestones: 12% scale-up matching USP slide
- Regulatory Landscape: 320→380+ in KPI card + subtitle (DE+EN)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 22:29:16 +02:00
Benjamin Admin e97c03587d feat(pitch-deck): scale up USP slide, update milestones, Compliance Optimizer module
Build pitch-deck / build-push-deploy (push) Successful in 1m13s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 37s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 35s
- USP: 12% scale-up for better screen utilization, larger title
- Milestones: correct dates (DPMA, domains, RAG, EUIPO, GmbH, customers)
- Product: AI Act Compliance → Compliance Optimizer

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 22:17:18 +02:00
Benjamin Admin 004a624f23 security: re-secure fp-patch
Build pitch-deck / build-push-deploy (push) Successful in 1m10s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 31s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 34s
2026-04-21 21:27:26 +02:00
Benjamin Admin 15b6e8614c feat(pitch-deck): milestones update, Serverkosten formula, material/liquidität fixes
Build pitch-deck / build-push-deploy (push) Successful in 1m38s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 44s
CI / test-bqas (push) Has been cancelled
CI / test-python-voice (push) Has been cancelled
- Milestones: correct dates + events (DPMA, domains, RAG, EUIPO, GmbH)
- Serverkosten: 2000 base + max(0, customers-10)*250 (first 10 included)
- Materialaufwand: cleared, info placeholder
- 2. Finanzierungsrunde: renamed, sort order fixed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 21:24:37 +02:00
Benjamin Admin 80376c90b3 security: re-secure fp-patch
Build pitch-deck / build-push-deploy (push) Successful in 1m26s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 49s
CI / test-python-voice (push) Successful in 44s
CI / test-bqas (push) Successful in 41s
2026-04-21 20:36:17 +02:00
Benjamin Admin 111e5d546f feat(pitch-deck): Pricing slide, GuV hierarchy, Problem/Solution cards, engine fixes
Build pitch-deck / build-push-deploy (push) Successful in 1m52s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-python-voice (push) Has been cancelled
CI / test-bqas (push) Has been cancelled
CI / test-go-consent (push) Has been cancelled
- BusinessModel → Pricing: remove Unit Economics, fullwidth tiers
- GuV: major sums (EBIT, Rohergebnis, Jahresüberschuss) larger font + border
- Engine: compute Rohergebnis, dynamic financing row matching
- Problem slide: amber/orange "Die Konsequenz" card
- Solution slide: larger Compliance Optimizer card
- DB patch: Stammkapital, 2. Finanzierungsrunde 500k, GuV sort order

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 20:33:35 +02:00
Benjamin Admin 43418d46fd security: re-secure fp-patch
Build pitch-deck / build-push-deploy (push) Successful in 1m10s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 31s
2026-04-21 19:41:30 +02:00
Benjamin Admin e4f2d49e96 fix(pitch-deck): engine uses dynamic row matching for renamed labels
Build pitch-deck / build-push-deploy (push) Successful in 1m16s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 33s
CI / test-bqas (push) Has been cancelled
CI / test-python-voice (push) Has been cancelled
- Engine no longer hardcodes financing row labels — matches by row_type
- Handles renamed WD rows (Wandeldarlehen Investor/L-Bank, Stammkapital)
- fp-patch: all pending WD fixes (labels, investments, materialaufwand, pos3)
- Bestandskunden annual column shows Dec value (point-in-time)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 19:39:00 +02:00
Benjamin Admin 898ad1785b security: re-secure fp-patch after WD data import
Build pitch-deck / build-push-deploy (push) Successful in 1m19s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 33s
CI / test-python-voice (push) Successful in 34s
CI / test-bqas (push) Successful in 33s
2026-04-21 19:16:39 +02:00
Benjamin Admin 8aa5db39fd fix(pitch-deck): engine includes manual revenue rows in GESAMTUMSATZ
Build pitch-deck / build-push-deploy (push) Successful in 1m19s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 33s
CI / test-python-voice (push) Successful in 34s
CI / test-bqas (push) Has been cancelled
Revenue rows without qty/price (e.g. Beratung & Service) were excluded
from total. Now all revenue rows contribute to GESAMTUMSATZ.
Same fix for materialaufwand SUMME.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 19:14:03 +02:00
Benjamin Admin db0b77ef8f fix(pitch-deck): restore WD revenue data, fix Bestandskunden annual display
Build pitch-deck / build-push-deploy (push) Successful in 1m14s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 36s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 39s
- Bulk-imported 67 umsatz + 78 kunden + 28 material rows to production WD
- Fix Bestandskunden annual column: show Dec value (point-in-time), not sum
- Fix tab labels: Umsatzerlöse, Liquidität (umlauts)
- Re-secure fp-patch endpoint

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 18:57:13 +02:00
Benjamin Admin 0d123d8264 chore: fp-patch — bulk import WD data from Mac Mini
Build pitch-deck / build-push-deploy (push) Successful in 1m17s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Has been cancelled
2026-04-21 18:54:22 +02:00
Benjamin Admin 4abba96515 chore: fp-patch — copy missing umsatz/kunden/material to WD
Build pitch-deck / build-push-deploy (push) Successful in 1m11s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 37s
2026-04-21 18:51:04 +02:00
Benjamin Admin c49fae8776 chore: diagnose Umsatzerlöse on production
Build pitch-deck / build-push-deploy (push) Successful in 1m15s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 36s
CI / test-python-voice (push) Successful in 34s
CI / test-bqas (push) Has been cancelled
2026-04-21 18:48:15 +02:00
Benjamin Admin 9a750eb2b1 chore: fp-patch — delete dup Rechtsanwalt + fix name umlauts
Build pitch-deck / build-push-deploy (push) Successful in 1m13s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 31s
2026-04-21 18:41:46 +02:00
Benjamin Admin 4c2a7574e4 chore: debug fp-patch Rechtsanwalt
Build pitch-deck / build-push-deploy (push) Successful in 1m15s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-bqas (push) Has been cancelled
CI / test-python-voice (push) Has started running
2026-04-21 18:39:28 +02:00
Benjamin Admin f115b0a307 chore: TEMP fp-patch — move Rechtsanwalt to 2030
Build pitch-deck / build-push-deploy (push) Successful in 1m17s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 35s
CI / test-bqas (push) Has been cancelled
CI / test-python-voice (push) Has been cancelled
2026-04-21 18:37:15 +02:00
Benjamin Admin c34c06d28d security: re-secure fp-patch
Build pitch-deck / build-push-deploy (push) Successful in 1m16s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 38s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 33s
2026-04-21 18:33:40 +02:00
Benjamin Admin 48042bde47 fix(pitch-deck): fix umlauts in tab labels + DB rows, delete Full-Stack pos
Build pitch-deck / build-push-deploy (push) Successful in 1m12s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 36s
CI / test-python-voice (push) Has been cancelled
CI / test-bqas (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 18:31:43 +02:00
Benjamin Admin 7fb207cfce security: re-secure fp-patch after execution
Build pitch-deck / build-push-deploy (push) Successful in 1m19s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 32s
2026-04-21 18:24:38 +02:00
Benjamin Admin 11b330c268 chore: TEMP fp-patch v3 — Fremdkapital fix + Rechtsanwalt + recompute
Build pitch-deck / build-push-deploy (push) Successful in 1m15s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 33s
CI / test-bqas (push) Has been cancelled
CI / test-python-voice (push) Has been cancelled
2026-04-21 18:22:31 +02:00
Benjamin Admin fb53c8be90 fix(anchor-finder): use correct Qdrant payload fields (regulation_id, regulation_name_de)
Qdrant collections use regulation_id (not regulation_code), regulation_name_de,
guideline_name, download_url etc. Also search bp_compliance_datenschutz
collection where OWASP/ENISA docs live.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 18:17:36 +02:00
Benjamin Admin b29dc33708 fix(control-pipeline): anchor finder uses direct Qdrant search instead of Go SDK
The Go SDK RAG proxy returns 401 (Qdrant API key mismatch). Switch
AnchorFinder to use direct Qdrant vector search + embedding service,
same approach as the main pipeline. No dependency on Go SDK anymore.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 18:13:12 +02:00
Benjamin Admin 7cb79dacd5 security(pitch-deck): re-secure fp-patch, convert to admin recompute endpoint
Build pitch-deck / build-push-deploy (push) Successful in 1m13s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 37s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 34s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 18:12:24 +02:00
Benjamin Admin 14362cbc0e chore(pitch-deck): TEMP public fp-patch v2 — fix WD funding + recompute
Build pitch-deck / build-push-deploy (push) Successful in 1m14s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 18:09:35 +02:00
Benjamin Admin 91f4202e88 feat(control-pipeline): add anchor backfill endpoint + normalize target_audience
- Add POST /v1/canonical/generate/backfill-anchors endpoint for batch
  populating open_anchors on controls generated with skip_web_search=true
- Uses AnchorFinder Stage A (RAG search) to find OWASP/NIST/ENISA refs
- Background job with progress tracking (same pattern as other backfills)
- Promotes needs_review controls that gain anchors to draft state
- Target audience normalization (enterprise/authority/provider → JSON arrays)
  already applied via SQL

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 18:04:50 +02:00
Benjamin Admin e5bb8e65e8 security(pitch-deck): remove temp public fp-patch, re-secure admin endpoint
Build pitch-deck / build-push-deploy (push) Successful in 1m14s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 30s
Patch executed successfully on production. Temp endpoint removed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 17:49:31 +02:00
Benjamin Admin f86dc265eb chore(pitch-deck): TEMP public fp-patch endpoint for one-time DB fix
Build pitch-deck / build-push-deploy (push) Successful in 1m12s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 34s
CI / test-bqas (push) Successful in 32s
Will be removed immediately after execution.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 17:46:26 +02:00
Benjamin Admin ae31a19275 Merge branch 'main' of ssh://gitea.meghsakha.com:22222/Benjamin_Boenisch/breakpilot-core
Build pitch-deck / build-push-deploy (push) Successful in 1m28s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 41s
CI / test-python-voice (push) Successful in 34s
CI / test-bqas (push) Has been cancelled
2026-04-21 17:43:08 +02:00
Benjamin Admin e72b68b4a3 chore(pitch-deck): TEMP disable auth on fp-patch for one-time execution
Will be re-secured immediately after running.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 17:42:51 +02:00
Sharang Parnerkar 6b08ce6b6a pitch-deck: fix HEUTE pill clipped behind milestone cards
Build pitch-deck / build-push-deploy (push) Successful in 1m6s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 31s
CI / test-python-voice (push) Successful in 27s
CI / test-bqas (push) Successful in 33s
Move pill from SVG (behind HTML) to an absolutely-positioned HTML div
with zIndex:10 so it always renders above the milestone cards.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 11:31:23 +02:00
Sharang Parnerkar e0a3ff5ca9 pitch-deck: fix timeline overlap with tip/progress badges in MilestonesSlide
Build pitch-deck / build-push-deploy (push) Successful in 1m9s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 31s
CI / test-python-voice (push) Successful in 31s
CI / test-bqas (push) Successful in 29s
Increase timeline marginTop from 14→68 so it clears the absolute-positioned
Tip and Fortschritt elements at top:36.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 11:20:41 +02:00
Sharang Parnerkar b3baf603ee pitch-deck: remove duplicate in-canvas headings from USP and Milestones slides
Build pitch-deck / build-push-deploy (push) Successful in 1m9s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 28s
CI / test-bqas (push) Successful in 31s
Keep only the top-level GradientText h2; drop the redundant h1 and kicker
inside each slide canvas.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 11:11:38 +02:00
Sharang Parnerkar ad2bbab7b6 pitch-deck: remove unused TractionSlide import
Build pitch-deck / build-push-deploy (push) Successful in 1m12s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 30s
CI / test-bqas (push) Successful in 28s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 10:59:34 +02:00
Sharang Parnerkar a3a1ec4430 pitch-deck: wire MilestonesSlide to traction slide slot
Build pitch-deck / build-push-deploy (push) Successful in 1m6s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 28s
CI / test-bqas (push) Successful in 28s
Replace old TractionSlide with new MilestonesSlide on the 'traction' route.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 10:55:03 +02:00
Sharang Parnerkar c0c44adaaa pitch-deck: light mode support + MilestonesSlide redesign
Build pitch-deck / build-push-deploy (push) Successful in 1m12s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 28s
CI / test-bqas (push) Successful in 32s
- ArchitectureSlide: full light mode via useIsLight() hook, all inline styles adapt
- USPSlide: full light mode via useIsLight() hook, all inline styles adapt
- MilestonesSlide: new component — horizontal timeline with past/HEUTE/future,
  THEMES object (dark + light), clickable milestone nodes and stat cards with
  detail modal, bilingual (de/en), scaling via ResizeObserver
- PitchDeck: register new 'milestones' slide case

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 10:34:36 +02:00
Sharang Parnerkar 8a4e196864 feat(pitch-deck): redesign USP slide with symmetric bridge composition
Build pitch-deck / build-push-deploy (push) Successful in 1m10s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 31s
CI / test-bqas (push) Successful in 30s
Port Claude Design's USP v2 exactly: Compliance ↔ Code bridge with a
central glowing orb, two pillar rows per column, animated SVG flow
connectors, and four Under-the-Hood feature cards with live tickers.

Detail modal (Framer Motion AnimatePresence) slides in on click for
all 9 interactive elements. Star field background. All text is our
actual content (RFQ Verification, Process Compliance, Bidirectional
Sync, Continuous, End-to-End Traceability, Compliance Optimizer,
EU Trust Stack, killer quote). Full DE/EN i18n.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 10:03:18 +02:00
Sharang Parnerkar 34e2614e36 feat(pitch-deck): redesign architecture slide with V4 layered stack
Build pitch-deck / build-push-deploy (push) Successful in 1m27s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 41s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 31s
Port Claude Design's V4 design exactly: three 3D perspective slabs
(Application / Gateway / Infrastructure) with animated data-flow connectors
between them, per-node live activity tickers, and a slide-up detail panel.

Replaces metro map with V4's floating slab aesthetic — dark purple gradient
background, CSS perspective rotateX transforms, JetBrains Mono terminal
tickers, amber LiteLLM hub with pulse indicator. All node data (titles,
tech stacks, services) preserved from previous design.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 09:06:45 +02:00
Sharang Parnerkar ac8ef371ff fix(pitch-deck): center mandants strip and fix LiteLLM overlap
Build pitch-deck / build-push-deploy (push) Successful in 1m12s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 33s
CI / test-python-voice (push) Successful in 31s
CI / test-bqas (push) Successful in 30s
- Remove flex-1 trailing divider that was pushing pills left
- Increase map height 420→480px to give clearance between app labels and gateway circle
- Update SVG viewBox to 0 0 1100 480 (consistent with CONNS coordinates)
- Update cy% for all nodes to match new 480px coordinate space (app 22.1%, gateway 50%, inference 80%)
- Update SEP1/SEP2 to 36%/65% for new height
- Update all SVG element y-coords: track lines, tick marks, junction dots, gateway stub

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 23:48:33 +02:00
Sharang Parnerkar e37aecab18 redesign ArchitectureSlide with metro/subway map theme
Build pitch-deck / build-push-deploy (push) Successful in 1m17s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 31s
CI / test-bqas (push) Successful in 32s
- Replace rectangular cards with circular station nodes on metro tracks
- Right-angle SVG paths only (no distortion with preserveAspectRatio=none)
- Animated data packet flows along tracks, bidirectional MCP arc
- LiteLLM hub enlarged (82px), centered mandants strip
- Vertical tier labels, BSI badges, junction corner dots
- Preserve all node data, detail panel, i18n, TokenTicker

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 23:39:46 +02:00
Sharang Parnerkar ecc1423a4f redesign ArchitectureSlide: COMPLAI/CERTifAI/Scanner + EU-only infra
Build pitch-deck / build-push-deploy (push) Successful in 1m12s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 33s
- New architecture: CERTifAI (GenAI portal), COMPLAI (compliance), Scanner
  (code security) — MCP bidirectional connection between COMPLAI and Scanner
- LiteLLM hub: token budget, PII guardrails, anon web search, no US providers
- Inference layer: Qwen3/DeepSeek (local LLM), bge-m3 embeddings, AI Tools
- Fix hover snapping: position wrapper (div) separate from scale (motion.button)
- Always-on data traffic packets on all connections
- Bidirectional MCP packets + MCP badge between COMPLAI and Scanner
- Live token ticker counter on active inference nodes
- BSI/EU sovereign badges on tier labels
- Spinning dashed ring on active LiteLLM hub
- Secondary infra chips in GenAI tier (PostgreSQL, Gitea, Orca, etc.)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 22:46:40 +02:00
Sharang Parnerkar 39fcf58d1b fix bad merge: remove duplicate slide names + move CSS imports before tailwind
Build pitch-deck / build-push-deploy (push) Successful in 1m14s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 35s
Merge conflict resolution incorrectly added Anhang: Annahmen and
Anhang: Go-to-Market twice. Remove the duplicates, keep only the
renamed Anhang: Systemarchitektur entry in the correct position.
Also move @import rules above @tailwind directives (Turbopack CSS spec).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 22:31:47 +02:00
Sharang Parnerkar 497be5fac9 redesign ArchitectureSlide with island map aesthetic + turbopack dev
Build pitch-deck / build-push-deploy (push) Successful in 1m36s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 41s
CI / test-python-voice (push) Successful in 37s
CI / test-bqas (push) Successful in 33s
- Replace grid/SVG-line layout with archipelago map: organic island blobs,
  quadratic bezier sea routes, circular map-marker nodes
- Fix SVG distortion: all strokes use vectorEffect=non-scaling-stroke
- No more preserveAspectRatio=none diagonal-line warping
- LiteLLM hub gets spinning ring + ripple pulse on active
- Ocean background with per-tier radial glows, dot grid, zone labels
- Switch dev server to --turbopack for faster HMR

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 22:26:12 +02:00
Benjamin Admin 855e764911 feat(pitch-deck): add fp-patch admin endpoint for insurance cost halving
Build pitch-deck / build-push-deploy (push) Successful in 1m12s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 34s
CI / test-bqas (push) Successful in 35s
One-time endpoint to halve D&O, E&O, Produkthaftpflicht, Cyber, Rechtsschutz
for 2027 and delete Editorial Content row.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 20:31:27 +02:00
Benjamin Admin e151984ce2 fix(pitch-deck): remove Finanzprognose slide from deck
Build pitch-deck / build-push-deploy (push) Successful in 1m14s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 35s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 32s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 20:22:27 +02:00
Benjamin Admin 11431bbf4e feat(pitch-deck): feature matrix grouped by theme, stars for unique features
Build pitch-deck / build-push-deploy (push) Successful in 1m19s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 35s
CI / test-python-voice (push) Successful in 37s
CI / test-bqas (push) Successful in 32s
- Group features into 7 thematic sections with colored headers
- Set isDiff=true (star) for all features where only ComplAI has it
- USP slide: remove "ohne es zu brechen" from quote
- Product slide: Privacy-Hardware card colored (emerald) like Cloud card

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 20:17:40 +02:00
Benjamin Admin fcac514d9f fix(pitch-deck): GESAMTUMSATZ sum, Engineering stats, betriebliche accordion
Build pitch-deck / build-push-deploy (push) Successful in 1m10s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 33s
CI / test-python-voice (push) Successful in 29s
CI / test-bqas (push) Successful in 31s
- Fix GESAMTUMSATZ: only sum section='revenue' rows (not price/quantity)
- Fix Materialaufwand SUMME: only sum section='cost' rows
- Kunden GESAMT: trust DB values instead of wrong frontend recompute
- Engineering: 500K+ LoC, 385 RAG docs, 25K+ controls, remove modules card
- Betriebliche Aufwendungen: clickable category headers with ▸/▾ toggle

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 20:05:27 +02:00
Benjamin Admin da4dcdca32 fix(pitch-deck): remove Use of Funds, GTM slide; move Assumptions after Finanzplan
Build pitch-deck / build-push-deploy (push) Successful in 1m11s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 38s
CI / test-bqas (push) Successful in 33s
- Remove Use of Funds card from The Ask slide
- Remove Go-to-Market slide from deck
- Move Annahmen & Sensitivität after Finanzplan in slide order
- Update sidebar names in both DE and EN

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 19:57:41 +02:00
Benjamin Admin 3cce3b2871 fix(pitch-deck): SolutionSlide crash + CompetitionSlide cleanup
Build pitch-deck / build-push-deploy (push) Successful in 1m19s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 36s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 34s
- Fix ReferenceError on slide 5: add missing `const de = lang === 'de'`
- Remove feature matrix rows: Hardware Moat, 380+ Regularien, Self-Hosted, Multi-Framework Consent SDK
- Rename IPFS → IPFS/Blockchain (optional)
- Remove Pricing-Einordnung cards and Top 5 Unterschiede accordion

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 19:52:32 +02:00
Benjamin Admin a2c5307713 fix(pitch-deck): chart label sizes + negative value positions
Build pitch-deck / build-push-deploy (push) Successful in 1m16s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 33s
CI / test-python-voice (push) Successful in 43s
CI / test-bqas (push) Successful in 44s
- Kunden labels: 7px → 11px (matching MRR)
- EBIT negative: moved from inside bar (mt-1) to above bar (-mt-4)
- Liquidität negative: same fix (-mt-4 for all values)
- grossMargin + nrr added to FinanzplanSlide KPIs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 19:28:36 +02:00
Benjamin Admin ef7ec776eb fix(pitch-deck): add grossMargin + nrr to FinanzplanSlide KPIs
Build pitch-deck / build-push-deploy (push) Successful in 1m12s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 35s
CI / test-python-voice (push) Successful in 43s
CI / test-bqas (push) Has been cancelled
Charts tab showed 0 for Gross Margin and NRR because these fields
were not computed in the FinanzplanSlide's own fpKPIs loading
(only existed in the useFpKPIs shared hook).

Added: grossMargin = (revenue - material) / revenue × 100
Added: nrr = revenue / prevYearRevenue × 100

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 19:25:22 +02:00
Benjamin Admin 9fd829eceb fix(pitch-deck): reorder betriebliche Aufwendungen + remove footer
Build pitch-deck / build-push-deploy (push) Successful in 1m12s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 37s
CI / test-python-voice (push) Successful in 41s
CI / test-bqas (push) Successful in 32s
New order: Detail rows (Steuern, Versicherungen, Raum, Marketing,
Besondere, Fahrzeug, Sonstige) → Summe sonstige → Personalkosten →
Abschreibungen → SUMME Betriebliche Aufwendungen

Removed auto-generated SUMME footer for betriebliche tab
(redundant with SUMME Betriebliche row).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 19:08:21 +02:00
Benjamin Admin 96cd79dec5 fix(pitch-deck): AppSec tab — remove Compliance USPs, reorder sections
Build pitch-deck / build-push-deploy (push) Successful in 1m11s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 32s
- Removed USP section (GDPR, AI Act, CRA etc.) — these are Compliance, not AppSec
- Removed Compliance-only features from APPSEC_FEATURES array
- Reordered: Competitors → Effizienz-Kennzahlen → AppSec Features → Score Summary

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 19:03:51 +02:00
Benjamin Admin fde1673bdd fix(pitch-deck): Competition slide — add Effizienz to AppSec tab + fix pricing tiers
Build pitch-deck / build-push-deploy (push) Successful in 1m10s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 37s
CI / test-bqas (push) Successful in 30s
AppSec tab: Added Effizienz-Kennzahlen table (was only in Overview tab).

Pricing tab: ComplAI pricing corrected:
- 4 tiers → 3 tiers (Starter/Professional/Enterprise matching Folie 11)
- Enterprise: €40k → €50k/yr
- "110 Regularien" → "380+ Regularien"
- "DE / FR" → "DE" (Frankreich removed)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 18:29:27 +02:00
Benjamin Admin f781874eee fix(pitch-deck): Financials Overview tab fully from fp_* data
Build pitch-deck / build-push-deploy (push) Successful in 1m11s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 31s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 29s
Replaced all useFinancialModel-based charts with fp_*-based:
- Revenue vs Costs: annual bars from fpKPIs (was FinancialChart/monthly)
- EBIT & Liquidität: dual bar chart (was WaterfallChart)
- Unit Economics 2030: ACV, Gross Margin, NRR, EBIT Margin (was RunwayGauge + UnitEconomicsCards)

All 3 tabs (Overview, GuV, Cashflow) now read from fp_* tables.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 18:21:23 +02:00
Benjamin Admin 9333b7a9c3 feat(pitch-deck): Unit Economics chart in Finanzplan + larger sub-text + remove Container metric
Build pitch-deck / build-push-deploy (push) Successful in 1m20s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-python-voice (push) Has been cancelled
CI / test-bqas (push) Has been cancelled
CI / test-go-consent (push) Has been cancelled
Finanzplan Charts tab: new Unit Economics chart showing ACV, Gross Margin,
NRR, EBIT Margin as mini bar charts per year (2026-2030).

BusinessModelSlide: sub-text under Unit Economics increased from 10px → xs (12px).

DB: Removed "Container in Produktion" metric from pitch_metrics + all versions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 18:19:23 +02:00
Benjamin Admin 6db0056329 feat(pitch-deck): pipeline stats from DB on Folie 2 + Optimizer on Folie 5 + quote fix
Build pitch-deck / build-push-deploy (push) Successful in 1m9s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 33s
Folie 2 (Executive Summary):
- KPIs (Controls, Regulations) now from pitch_pipeline_stats DB
- "110 Gesetze" → dynamic from DB (380+)

Folie 5 (Solution):
- Added Compliance Optimizer banner below 3 pillars
- "Nicht nur erlaubt/verboten — maximale Ausgestaltung jedes KI-Use-Cases"

USP Slide:
- Quote fix: "wie weit du maximal gehen kannst" (was: "wie du maximal weit gehen kannst")

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 18:11:49 +02:00
Benjamin Admin e0fedde560 feat(pitch-deck): 4 MOAT points on Executive Summary (Folie 2)
Build pitch-deck / build-push-deploy (push) Successful in 1m9s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 32s
Replaced generic USP banner with compact 4-column MOAT grid:
1. Traceability: Gesetz → Control → Code
2. Continuous Engine: Echtzeit bei jeder Änderung
3. Compliance Optimizer: Maximale KI-Nutzung im Rahmen
4. EU-Trust Stack: 100% EU, kein US-SaaS

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 18:07:34 +02:00
Benjamin Admin f7441ccba5 feat(pitch-deck): Compliance Optimizer as 4th MOAT on USP slide + competitor fix
Build pitch-deck / build-push-deploy (push) Successful in 1m12s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-python-voice (push) Has been cancelled
CI / test-bqas (push) Has been cancelled
CI / test-go-consent (push) Has started running
USP Slide:
- 4 MOAT cards (was 3): added "Compliance Optimizer"
  "Not just allowed/forbidden but the maximum permissible configuration
  of every AI use case. Deterministic constraint optimization."
- Killer quote: "Everyone can say what is forbidden. Almost no one can
  say how far you can go without breaking it. That is our product."
- Grid: 3 cols → 2x2 for better readability

Executive Summary:
- Competitors: removed Invest column, larger font (9px → 10px)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 18:05:45 +02:00
Benjamin Admin 27d1c5ba9f fix(pitch-deck): remove Invest column from competitors, increase font size
Build pitch-deck / build-push-deploy (push) Successful in 1m6s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 29s
CI / test-python-voice (push) Successful in 37s
CI / test-bqas (push) Has been cancelled
Executive Summary competitors table:
- Removed "Invest" column (6 cols → 5 cols)
- Font size: 9px → 10px for data, 8px → 9px for headers
- More spacing (gap-x-3, space-y-1.5)
- ARR column bold for emphasis

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 18:03:00 +02:00
Benjamin Admin 8354ab4df4 chore: trigger deploy
Build pitch-deck / build-push-deploy (push) Successful in 1m13s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 33s
CI / test-python-voice (push) Successful in 29s
CI / test-bqas (push) Successful in 34s
2026-04-20 17:54:06 +02:00
Benjamin Admin 88d0619184 fix(pitch-deck): SUMME Betriebliche includes Personalkosten + Abschreibungen
Build pitch-deck / build-push-deploy (push) Successful in 1m7s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 33s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 33s
Bug: "SUMME Betriebliche Aufwendungen" excluded Personalkosten and
Abschreibungen because they have is_sum_row=true. Result: both sum
rows showed identical values.

Fix: explicitly include Personalkosten and Abschreibungen rows in
the SUMME Betriebliche calculation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 10:54:19 +02:00
Benjamin Admin 6111494460 fix(pitch-deck): remove Berechnen button + cell editing from Finanzplan
Build pitch-deck / build-push-deploy (push) Successful in 1m10s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 29s
CI / test-python-voice (push) Successful in 30s
CI / test-bqas (push) Has been cancelled
Finanzplan is now read-only for investors:
- Removed "Berechnen" / "Compute" button
- Removed cell double-click editing
- Removed blue edit indicator dots
- All sums computed live in frontend (no manual recompute needed)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 10:51:43 +02:00
Benjamin Admin 73e3749960 fix(pitch-deck): SUMME footer works in both annual and monthly view
Build pitch-deck / build-push-deploy (push) Successful in 1m4s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 29s
CI / test-bqas (push) Successful in 27s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 10:46:16 +02:00
Benjamin Admin f57bdfa151 chore: trigger deploy
Build pitch-deck / build-push-deploy (push) Successful in 1m9s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 29s
2026-04-20 10:42:51 +02:00
Benjamin Admin 34b519eebb fix(pitch-deck): Investitionen tab shows values (was empty due to values_invest field)
Build pitch-deck / build-push-deploy (push) Successful in 1m2s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 27s
CI / test-python-voice (push) Successful in 31s
CI / test-bqas (push) Successful in 31s
getValues() now reads values_invest for investment rows.
Previously only read values/values_total/values_brutto, missing the
invest-specific field name.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 10:38:03 +02:00
Benjamin Admin 66fb265f22 feat(pitch-deck): collapsible year view in Finanzplan + remove section labels
Build pitch-deck / build-push-deploy (push) Successful in 1m6s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 30s
CI / test-python-voice (push) Successful in 31s
CI / test-bqas (push) Successful in 31s
- Year navigation: "Alle Jahre" shows 5 annual columns, individual years show 12 months
- Default starts at single year view
- Annual view: flow rows show yearly sum, balance rows show Dec value
- Removed [section] labels from row display
- Footer sum only shown in monthly view (not annual)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 10:31:47 +02:00
Benjamin Admin ec7326cfe1 feat(pitch-deck): live-compute sums for Liquidität + Kunden + Umsatz tabs
Build pitch-deck / build-push-deploy (push) Successful in 1m6s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 31s
CI / test-python-voice (push) Successful in 30s
CI / test-bqas (push) Successful in 32s
Extended live-compute to ALL tabs:
- Liquidität: "Summe ERTRÄGE" = sum of einzahlung rows,
  "Summe AUSZAHLUNGEN" = sum of auszahlung rows
- Kunden: GESAMT rows = sum of tier detail rows
- Umsatz: GESAMTUMSATZ = sum of all revenue rows
- Materialaufwand: SUMME = sum of cost rows

ÜBERSCHUSS rows kept from DB (complex multi-step formula).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 10:26:23 +02:00
Benjamin Admin 67ed5e542d feat(pitch-deck): live-computed sum rows in Finanzplan (like Excel formulas)
Build pitch-deck / build-push-deploy (push) Successful in 1m11s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 29s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Has been cancelled
Sum rows (is_sum_row=true) are now computed live in the frontend from
their detail rows, not read from stale DB values. This means:
- Category sums (Versicherungen, Marketing, Sonstige etc.) always match
- "Summe sonstige" = all non-personal, non-AfA rows
- "SUMME Betriebliche" = all rows including personal + AfA
- No more manual recompute needed after DB changes

Also: chart labels increased from 7-8px to 11px for readability.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 10:23:59 +02:00
Benjamin Admin 6ec27fdbf2 feat(pitch-deck): larger chart labels + 2 new charts (Liquidität + Revenue vs Costs)
Build pitch-deck / build-push-deploy (push) Successful in 1m6s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 33s
CI / test-python-voice (push) Successful in 34s
CI / test-bqas (push) Successful in 28s
Charts tab:
- All bar labels increased from 7-8px to 11px (readable)
- New: Liquidität (Jahresende) bar chart — shows cash position per year
- New: Umsatz vs. Gesamtkosten — side-by-side bars per year
- All charts read from fpKPIs (fp_* source of truth)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 10:14:39 +02:00
Benjamin Admin 9513675d85 fix(pitch-deck): Finanzplan starts on GuV tab instead of Personalkosten
Build pitch-deck / build-push-deploy (push) Successful in 1m8s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 28s
CI / test-python-voice (push) Successful in 27s
CI / test-bqas (push) Successful in 29s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 10:11:39 +02:00
Benjamin Admin 8e2329be53 fix(pitch-deck): trial churn (25% leave after 3 months) + remove unit_cost label
Build pitch-deck / build-push-deploy (push) Successful in 1m4s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 30s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 31s
Churn model: 25% of new customers leave after 3 months (trial period).
Remaining customers have normal monthly churn (3% Starter, 2% Pro, 1% Ent).
Churn label shows "25% Trial + X%/Mon".

DB: section 'unit_cost' renamed to 'einkauf' (removed English label from UI).
Code: unit price detection updated for new section name.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 10:07:45 +02:00
Benjamin Admin 19214bfd66 fix(pitch-deck): remove Sonst. Erträge tab + add investments
Build pitch-deck / build-push-deploy (push) Successful in 1m7s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 30s
CI / test-python-voice (push) Successful in 34s
CI / test-bqas (push) Successful in 33s
- Removed 'sonst_ertraege' from SHEET_LIST (empty, irrelevant for pre-seed)
- DB: Added Mac Studio (LLM Training) 13.000 EUR, Jan 2027, AfA 3 Jahre
- DB: Added Software-Lizenzen (GWG) 800 EUR/Jahr (2026-2030)
- DB: Added Domain/SSL/Zertifikate (GWG) 500 EUR at founding
- DB: Removed GPU-Server (wrong assumption — Mac Studio used instead)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 09:52:19 +02:00
Benjamin Admin 53e61c6dcd fix(pitch-deck): no costs before founding month (FOUNDING_MONTH)
Build pitch-deck / build-push-deploy (push) Successful in 1m8s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 31s
Engine: All formula-based rows (F) now start at FOUNDING_MONTH (m8),
not m1. Affects: Fortbildung, Fahrzeug, KFZ, Reise, Bewirtung,
Internet, BG, Marketing, Serverkosten, Gewerbesteuer.

DB: All manual (M) betriebliche rows zeroed for m1-m7 across all
6 scenarios.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 09:45:45 +02:00
Benjamin Admin 728f698f9e fix(pitch-deck): remove redundant Summe rows from Umsatz/Material/Kunden tabs + total line styling
Build pitch-deck / build-push-deploy (push) Successful in 1m7s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 33s
CI / test-python-voice (push) Successful in 29s
CI / test-bqas (push) Successful in 34s
- Removed auto-generated SUMME footer from umsatzerloese, materialaufwand, kunden tabs
  (GESAMTUMSATZ/Bestandskunden gesamt rows already exist in DB data)
- GESAMT/Total rows now have thicker top border (border-t-2 white/20)
- unit_cost rows show unit price instead of annual sum

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 09:42:16 +02:00
Benjamin Admin 511a7de627 fix(pitch-deck): unit_cost rows show price not annual sum in Finanzplan
Build pitch-deck / build-push-deploy (push) Successful in 1m10s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 31s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 28s
Einkaufspreis rows (Mac Mini/Studio) showed sum of 12 months (e.g. 38,400)
instead of the unit price (3,200). Now detected via section='unit_cost'
or label contains 'Einkaufspreis' and shows the price value instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 09:36:42 +02:00
Benjamin Admin 9d82f15c53 fix(pitch-deck): FinanzplanSlide selects correct fp_scenario per version
Build pitch-deck / build-push-deploy (push) Successful in 1m8s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 33s
Bug: Finanzplan data grid always loaded Base Case (is_default=true)
even for Wandeldarlehen version, showing 35 employees + module-based
customers instead of lean 10-person plan.

Fix: isWandeldarlehen prop passed to FinanzplanSlide. On load, picks
Wandeldarlehen scenario by name match instead of is_default.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 20:37:00 +02:00
Benjamin Admin b0918fd946 fix(pitch-deck): GuV + Cashflow tabs read from fp_* data, Break-Even as year
Build pitch-deck / build-push-deploy (push) Successful in 1m31s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 40s
CI / test-python-voice (push) Successful in 37s
CI / test-bqas (push) Successful in 31s
FinancialsSlide:
- Break-Even: shows year (2029) instead of formatted number (2.029)
- GuV tab: replaced AnnualPLTable (useFinancialModel) with fp_guv data table
  Shows: Revenue, Personnel, EBIT, Taxes, Net Income per year
- Cashflow tab: replaced AnnualCashflowChart (useFinancialModel) with
  fp_liquiditaet bar chart showing cash position + EBIT per year
- Both tabs now show "Quelle: Finanzplan" label

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 20:33:30 +02:00
Benjamin Admin 7b31b462a0 fix(pitch-deck): 1 Mio investment amount everywhere (975k → 1M)
Build pitch-deck / build-push-deploy (push) Failing after 25s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Failing after 7s
CI / test-python-voice (push) Failing after 10s
CI / test-bqas (push) Failing after 9s
Updated:
- CapTable: 975k → 1M, 19.6% → 20%, Gründer 37.5% → 37.3%
- FAQ: investment-captable answer updated to 1M
- Production DB: fp_liquiditaet Fremdkapital 975k → 1M (Base + Bear + Bull)
- Production DB: pitch_version_data funding amount → 1M
- All 3 scenarios (Base/Bear/Bull) recomputed with new amounts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 18:16:26 +02:00
Benjamin Admin 021faedfa3 fix(pitch-deck): CapTable slide — remove Gehälter/Gewinnverwendung, fix BAFA + 1M amounts
Build pitch-deck / build-push-deploy (push) Failing after 23s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Failing after 10s
CI / test-python-voice (push) Failing after 10s
CI / test-bqas (push) Failing after 11s
Removed:
- Gründergehälter card (entire section)
- Gewinnverwendung card (entire section)
- Instrument line from Pre-Seed Runde

Updated:
- Series A → "Series A Ausblick (Optional)"
- Investment: 975.000 → 1.000.000 EUR
- Post-Money: 4.975.000 → 5.000.000 EUR
- BAFA INVEST: 20% → 15% Erwerbszuschuss + 25% Exit-Zuschuss

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 18:07:30 +02:00
Benjamin Admin 0b30c5e66c feat(pitch-deck): Bear/Bull scenarios from DB + Assumptions slide reads all 3
Build pitch-deck / build-push-deploy (push) Failing after 21s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Failing after 6s
CI / test-python-voice (push) Failing after 9s
CI / test-bqas (push) Failing after 8s
4 new fp_scenarios created on production:
- Wandeldarlehen Bear (5%/8% growth, 1.5x churn → 11 customers 2030)
- Wandeldarlehen Bull (12%/16% growth, 0.7x churn → 999 customers 2030)
- 1 Mio Bear (6%/10% growth, 1.5x churn → 22 customers 2030)
- 1 Mio Bull (12%/18% growth, 0.7x churn → 2574 customers 2030)

AssumptionsSlide loads all 3 scenarios (Bear/Base/Bull) from their
respective fp_* tables. No more scaling factors — real DB data.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 18:04:59 +02:00
Benjamin Admin 824f8a7ff2 fix(pitch-deck): remove duplicate phases from GTM slide
Build pitch-deck / build-push-deploy (push) Failing after 25s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Failing after 11s
CI / test-python-voice (push) Failing after 10s
CI / test-bqas (push) Failing after 12s
Phases were duplicated between GTM slide and Strategy slide.
GTM now shows only: ICP (Ideal Customer Profile) + Channel Mix.
Phases remain exclusively on Strategy slide (version-aware).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 17:57:45 +02:00
Benjamin Admin 5914ec6cd5 feat(pitch-deck): AIPipeline slide numbers from pitch_pipeline_stats DB table
Build pitch-deck / build-push-deploy (push) Successful in 1m8s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Failing after 9s
CI / test-python-voice (push) Failing after 27s
CI / test-bqas (push) Failing after 27s
All KPI numbers on the AI Pipeline slide now load from the
pitch_pipeline_stats table via /api/pipeline-stats:
- Legal sources: 380+ (was hardcoded 75+)
- Unique controls: 25k+ (was 70k+)
- Obligations: 47k+ (from DB)
- EU regulations, DACH laws, frameworks: from DB
- Pipeline steps text: all counts dynamic

Numbers can be updated via SQL without code deploy:
UPDATE pitch_pipeline_stats SET value = X WHERE key = 'legal_sources';

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 17:55:22 +02:00
Benjamin Admin 30c63bbef6 feat(pitch-deck): Use of Funds computed from fp_* spending data
Build pitch-deck / build-push-deploy (push) Successful in 1m20s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Failing after 22s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 35s
Use of Funds pie chart now shows actual spending breakdown from fp_* tables
(months 8-24) instead of manually set percentages:
- Engineering & Personal: from fp_personalkosten
- Vertrieb & Marketing: from fp_betriebliche (marketing category)
- Betrieb & Infrastruktur: from fp_betriebliche (other categories)
- Hardware & Ausstattung: from fp_investitionen

Falls back to funding.use_of_funds if fp_* data not yet loaded.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 17:50:08 +02:00
Benjamin Admin 7be1a296c6 feat(pitch-deck): NRR + Payback formula-based from fp_* data
Build pitch-deck / build-push-deploy (push) Successful in 1m11s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 39s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Has been cancelled
- NRR: Revenue year N / Revenue year N-1 × 100 (no more "target > 120%")
- Payback: CAC / monthly gross profit (no more "target < 3 months")
- Both computed in useFpKPIs hook from fp_guv data
- BusinessModelSlide shows computed values with "(berechnet)" label

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 17:47:11 +02:00
Benjamin Admin e524786ac0 chore: trigger deploy after GuV recompute + isWandeldarlehen fix
Build pitch-deck / build-push-deploy (push) Successful in 1m48s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 35s
CI / test-python-voice (push) Successful in 41s
CI / test-bqas (push) Successful in 37s
2026-04-19 17:41:03 +02:00
Benjamin Admin 0ee2b1538a fix(pitch-deck): critical — isWandeldarlehen exact match, not includes
Build pitch-deck / build-push-deploy (push) Successful in 1m18s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 40s
CI / test-python-voice (push) Successful in 31s
CI / test-bqas (push) Successful in 42s
Bug: 1 Mio version instrument "Stammkapital + Wandeldarlehen + Equity"
matched includes('wandeldarlehen'), applying lean logic to 1 Mio version.

Fix: === 'wandeldarlehen' (exact match).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 17:11:57 +02:00
Benjamin Admin dd6e2f8bd7 feat(pitch-deck): MOAT on USP slide + version-aware GTM slide
Build pitch-deck / build-push-deploy (push) Successful in 1m11s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 37s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 36s
USP Slide:
- Added 3 MOAT statements as prominent section:
  1. End-to-End Traceability (Law → Obligation → Control → Code)
  2. Continuous Compliance Engine (every change, real-time evidence)
  3. EU-Trust & Governance Stack (sovereign, GDPR/AI Act native)

GTM Slide (version-aware):
- Wandeldarlehen: Founder sales → Content/SEO → Organic growth
  3 phases (Pilot → Organic → Scaling), no AEs in 2027
- 1 Mio: Direct sales + Channel (unchanged)
  3 phases with 5-20 KMU target, 2 AEs from 2027

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 17:06:14 +02:00
Benjamin Admin f66f32ee9d fix(pitch-deck): all financial slides now read from fp_* tables via useFpKPIs
Build pitch-deck / build-push-deploy (push) Successful in 1m18s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 32s
New shared hook: useFpKPIs — loads annual KPIs from fp_guv/liquiditaet/personal/kunden.
Replaces useFinancialModel (simplified model) for KPI display on all slides.

Slides updated:
- CompetitionSlide: "110 Gesetze" → "380+ Regularien & Normen"
- BusinessModelSlide: ACV + Gross Margin from fp_* (was useFinancialModel)
- ExecutiveSummarySlide: Unternehmensentwicklung from fp_* (was useFinancialModel)
- FinancialsSlide: KPI cards from fp_* (ARR, Customers, Break-Even, EBIT 2030)

All slides now show consistent numbers from the same source of truth (fp_* tables).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 17:00:27 +02:00
Benjamin Admin de308b7397 fix(pitch-deck): Assumptions slide reads KPIs from fp_* tables + version-aware text
Build pitch-deck / build-push-deploy (push) Successful in 1m18s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 35s
CI / test-python-voice (push) Successful in 37s
CI / test-bqas (push) Successful in 35s
- Base Case KPIs now loaded from fp_guv/fp_liquiditaet/fp_kunden (source of truth)
- Bear/Bull derived from Base with scaling factors
- Assumptions text conditional: Wandeldarlehen shows lean plan (3→10, 8%/15% growth),
  1 Mio shows original (5→35, aggressive growth)
- Removed dependency on useFinancialModel (simplified model)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 14:37:31 +02:00
Benjamin Admin 8402e57323 fix(pitch-deck): Umlaute in RiskSlide + Sidebar-Name für Risiken
Build pitch-deck / build-push-deploy (push) Successful in 1m25s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 38s
CI / test-python-voice (push) Successful in 40s
CI / test-bqas (push) Has been cancelled
- Alle ä/ö/ü in RiskSlide.tsx korrigiert
- slideNames in i18n.ts: 'Risiken & Mitigation' (DE) + 'Risks & Mitigation' (EN) hinzugefügt

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 14:34:06 +02:00
Benjamin Admin 1212f6ddfb feat(pitch-deck): version-aware Strategy slide (Wandeldarlehen vs 1 Mio)
Build pitch-deck / build-push-deploy (push) Successful in 1m16s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 37s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 33s
Strategy slide now shows different phases per pitch version:

Wandeldarlehen (lean):
- Phase 1: 3 Personen, ~60k ARR, Prototyp → Produktiv
- Phase 2: 4-5 Personen, ~200k ARR, erster Dev + Security
- Phase 3: 5-7 Personen, ~500k-1M ARR, Vertrieb + Break-Even
- Phase 4: 7-10 Personen, ~2-3M ARR, profitabel organisch

1 Mio (unchanged):
- Phase 1-4: 5→35 MA, 75k→10M ARR

Risks slide already visible for both versions (in slide order).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 11:49:31 +02:00
Benjamin Admin ac2299226a feat(pitch-deck): add Risks & Mitigation slide (vorletzte Folie)
Build pitch-deck / build-push-deploy (push) Successful in 1m15s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 37s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 33s
New slide with 6 risks and concrete mitigations:
1. AI Commoditization — Layer 2-6 moat, not Layer 1
2. US Platform Expansion — EU-only infrastructure, CLOUD Act barrier
3. Team/Key-Person Risk — Documentation, ESOP, early legal hire
4. Slow Customer Acquisition — Consulting revenue bridge, channel strategy
5. Regulatory Changes — Enlarges market, RAG indexes in days
6. Liquidity Risk — Organic growth, Pre-Seed BW option

Key quote: "We don't compete with AI. We compete with teams that
use AI better than we do."

Presenter script added for the risks slide.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 11:45:51 +02:00
Benjamin Admin 607dab4f26 fix(pitch-deck): KPIs + Charts on Folie 28 now read from fp_* tables directly
Build pitch-deck / build-push-deploy (push) Successful in 1m12s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 34s
CI / test-bqas (push) Successful in 29s
Previously KPIs/Charts used useFinancialModel (simplified model) which had
different assumptions than the fp_* tables (source of truth).

Now: KPIs tab loads from fp_guv, fp_liquiditaet, fp_personalkosten, fp_kunden
via API. Charts (MRR, EBIT, Headcount) also use fp_* data.

Removed dependency on useFinancialModel and computeAnnualKPIs for this slide.
Added Liquidität (Dez) row to KPIs table.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 08:37:46 +02:00
Benjamin Admin 3b8f9b595e fix(pitch-deck): lean cost structure for Wandeldarlehen scenario
Build pitch-deck / build-push-deploy (push) Successful in 1m15s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 35s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 34s
Engine formula adjustments (reduced for lean startup):
- Fortbildung: 500→300, Fahrzeug: 400→200, KFZ-Steuer: 50→25
- KFZ-Versicherung: 500→150, Reise: 100→75, Bewirtung: 200→100
- Serverkosten: 100/Kunde→50/Kunde, Basis 500→300

Tooltips updated to match new values.

DB (production): All (M) rows reduced to lean levels:
- Raumkosten: 5000→0 (remote, kein Büro)
- Versicherungen: ~1700→800/Mon (Startup-Tarife)
- Verbrauchsmaterial: 500→50, Werkzeuge: 300→100
- Rechts-/Beratung: nur Gründungskosten (m8-m10)

Result: Liquidität Ende 2027 ≈ 0 (4.496 EUR), Break-Even 2029.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 08:25:57 +02:00
Benjamin Admin 84a0280c52 feat(pitch-deck): Gewerbesteuer formula + BG/Marketing/Telefon engine formulas + tooltips
Build pitch-deck / build-push-deploy (push) Successful in 1m34s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 38s
CI / test-python-voice (push) Successful in 39s
CI / test-bqas (push) Successful in 41s
Engine:
- Gewerbesteuer (F): 12.25% of monthly profit (only when positive)
- Berufsgenossenschaft (F): 2.77% of brutto payroll
- Allgemeine Marketingkosten (F): 10% of revenue
- Internet/Mobilfunk (F): Headcount × 50 EUR/Mon

UI: Tooltip for Gewerbesteuer formula added.

DB changes (production):
- Gewerbesteuer: (M) → (F), auto-calculated
- Rechtsanwalt/Datenschutz: new hire Oct 2026, 7500 EUR brutto
- Beratung & Services: new revenue line (5k→30k/Mon)
- Investitionen: Home Office 2500 EUR per new hire
- Marketing Videos moved to marketing category
- Bank → Bank-/Kreditkartengebühren
- Jahresabschluss costs filled (1000-2000 EUR/year)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 14:29:38 +02:00
Benjamin Admin dc36e59d17 feat(pitch-deck): formula engine + tooltips for betriebliche Aufwendungen
Build pitch-deck / build-push-deploy (push) Successful in 1m27s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 36s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 34s
Engine formulas added:
- Berufsgenossenschaft (F): 2.77% of total brutto payroll (VBG IT rate)
- Internet/Mobilfunk (F): Headcount × 50 EUR/Mon
- Allgemeine Marketingkosten (F): 10% of monthly revenue

UI: Hover tooltips on all (F) and computed rows showing the formula.
SUMME matcher updated for renamed "SUMME Betriebliche Aufwendungen".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 14:15:16 +02:00
Benjamin Admin 9bb689b7e6 Merge branch 'main' of ssh://gitea.meghsakha.com:22222/Benjamin_Boenisch/breakpilot-core
Build pitch-deck / build-push-deploy (push) Successful in 1m22s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 37s
CI / test-python-voice (push) Successful in 40s
CI / test-bqas (push) Successful in 39s
2026-04-18 13:03:31 +02:00
Benjamin Admin d01a50a4b1 feat(pitch-deck): formula-based betriebliche rows in Finanzplan engine
Compute engine now auto-calculates these rows from headcount/customers:
- Fort-/Weiterbildungskosten (F): MA (excl. founders) × 500 EUR/Mon
- Fahrzeugkosten (F): MA (excl. founders) × 400 EUR/Mon
- KFZ-Steuern (F): MA (excl. founders) × 50 EUR/Mon
- KFZ-Versicherung (F): MA (excl. founders) × 500 EUR/Mon
- Reisekosten (F): Headcount × 100 EUR/Mon
- Bewirtungskosten (F): Enterprise-Kunden × 200 EUR/Mon
- Serverkosten Cloud (F): Bestandskunden × 100 EUR + 500 EUR Basis

Labels marked (F) for formula, (M) for manual in production DB.
Gesamtkosten matcher updated for renamed "SUMME Betriebliche Aufwendungen".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 13:03:18 +02:00
Sharang Parnerkar 51e75187ed feat(pitch-deck): add force recompute to bypass stale pitch_fm_results cache
Build pitch-deck / build-push-deploy (push) Successful in 1m1s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 27s
CI / test-python-voice (push) Successful in 25s
CI / test-bqas (push) Successful in 28s
Adds `force: true` body param to POST /api/financial-model/compute that
skips the cached results check and recomputes from assumptions directly.
Exposes this via a "Force Recompute" button on the scenario edit admin page,
so updating assumptions directly in the DB can be followed by a cache bust
without touching the UI assumption flow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 11:10:35 +02:00
Sharang Parnerkar e37fd3bbe4 fix: remove scenario dropdown from FinanzplanSlide
Build pitch-deck / build-push-deploy (push) Successful in 1m2s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 28s
CI / test-python-voice (push) Successful in 29s
CI / test-bqas (push) Successful in 26s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 11:04:26 +02:00
Sharang Parnerkar 11fa490599 fix: finanzplan scenario selector — load from API, no hardcoded UUID
Build pitch-deck / build-push-deploy (push) Successful in 1m4s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 29s
CI / test-python-voice (push) Successful in 27s
CI / test-bqas (push) Successful in 28s
Replaces the FM-name-based 'wandeldarlehen' hack with a proper scenario
picker. Scenarios are fetched from /api/finanzplan, default is selected
automatically. Dropdown appears when multiple scenarios exist.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 10:56:52 +02:00
Sharang Parnerkar 27ef21a4f0 feat: git SHA version badge in admin, fix finanzplan caching, drop gitea remote
Build pitch-deck / build-push-deploy (push) Successful in 1m4s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 28s
CI / test-python-voice (push) Successful in 28s
CI / test-bqas (push) Successful in 26s
- AdminShell: shows NEXT_PUBLIC_GIT_SHA in sidebar footer
- Dockerfile + build-pitch-deck.yml: pass --build-arg GIT_SHA at build time
- FinanzplanSlide: fetch with cache:no-store to always show current DB values
- finanzplan routes: Cache-Control: no-store to prevent CDN/proxy staling
- CLAUDE.md: remove dead gitea remote (only origin exists)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 10:47:51 +02:00
Sharang Parnerkar b3643ddee9 Merge branch 'main' of ssh://coolify.meghsakha.com:22222/Benjamin_Boenisch/breakpilot-core
Build pitch-deck / build-push-deploy (push) Successful in 1m2s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 26s
CI / test-python-voice (push) Successful in 29s
CI / test-bqas (push) Successful in 26s
2026-04-17 10:39:54 +02:00
Sharang Parnerkar 68b7660ce3 docs: replace all Coolify references with Orca across core repo
CI/CD pipeline migrated from Coolify to Orca.
Updated CLAUDE.md, pre-push-checks, docs-src, and pitch-deck scripts/slides.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 10:39:47 +02:00
Benjamin Admin 2d61911d98 chore: trigger pitch-deck CI + deploy
Build pitch-deck / build-push-deploy (push) Successful in 1m12s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 30s
CI / test-python-voice (push) Successful in 29s
CI / test-bqas (push) Successful in 31s
2026-04-17 10:23:31 +02:00
Benjamin Admin 9f642901ab chore: trigger pitch-deck CI build
Build pitch-deck / build-push-deploy (push) Successful in 1m17s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 34s
2026-04-17 09:53:47 +02:00
Benjamin Admin add7400b78 chore: retrigger CI for pitch-deck fm_scenarios fix
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 41s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 32s
2026-04-17 09:45:48 +02:00
Benjamin Admin 65cc5200ea chore: trigger coolify rebuild (fm_scenarios fix)
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 35s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 33s
2026-04-17 08:55:11 +02:00
Benjamin Admin ede93a7774 chore: trigger rebuild after build verification
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 33s
CI / test-python-voice (push) Successful in 34s
CI / test-bqas (push) Successful in 32s
2026-04-17 08:47:05 +02:00
Benjamin Admin bc020e9f64 Merge branch 'main' of ssh://gitea.meghsakha.com:22222/Benjamin_Boenisch/breakpilot-core
Build pitch-deck / build-push-deploy (push) Successful in 1m9s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 36s
CI / test-python-voice (push) Successful in 34s
CI / test-bqas (push) Successful in 33s
2026-04-17 08:36:07 +02:00
Benjamin Admin bad4659d5b fix(pitch-deck): include fm_scenarios in preview-data API response
The admin preview was not returning fm_scenarios/fm_assumptions,
so preferredScenarioId was always null and all financial slides
fell back to Base Case (1M) instead of the version's scenario.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 08:35:22 +02:00
Sharang Parnerkar e3b33ef596 docs: add AGENTS.python/go/typescript.md and pre-push check rules
CI / go-lint (push) Has been cancelled
CI / python-lint (push) Has been cancelled
CI / nodejs-lint (push) Has been cancelled
CI / test-go-consent (push) Has been cancelled
CI / test-python-voice (push) Has been cancelled
CI / test-bqas (push) Has been cancelled
Mandatory pre-push gates for all three language stacks with exact
commands, common pitfalls, and architecture rules. CLAUDE.md updated
with quick-reference section linking to the new files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 08:35:12 +02:00
Sharang Parnerkar 39255f2c9e fix(pitch-deck): hoist textLang const out of fetch object literal
Build pitch-deck / build-push-deploy (push) Successful in 1m8s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 30s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 32s
Syntax error: const declaration was inside the options object.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 08:16:19 +02:00
Benjamin Admin 030991cb9a chore: trigger rebuild 2
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-bqas (push) Has been cancelled
CI / test-python-voice (push) Has been cancelled
2026-04-17 08:15:13 +02:00
Benjamin Admin fa9b554f50 fix(pitch-deck): TTS letter spelling (CE/SAST/DAST) + Finanzplan slide loads version scenario
Build pitch-deck / build-push-deploy (push) Failing after 23s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 29s
TTS:
- CE → "C. E." for letter-by-letter pronunciation
- SAST → "S. A. S. T.", DAST → "D. A. S. T."

Finanzplan Slide 28:
- Data grid now loads Wandeldarlehen fp_scenario when active FM scenario
  contains "wandeldarlehen" (scenarioId=c0000000-...-000000000200)
- Base Case version continues to load default fp_scenario

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 08:10:18 +02:00
Benjamin Admin 788714ecec chore: trigger coolify rebuild
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 42s
2026-04-17 08:05:09 +02:00
Benjamin Admin 08ca17c876 fix(pitch-deck): presenter script — prototype status, no production claims before Aug 2026
Build pitch-deck / build-push-deploy (push) Failing after 22s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 35s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 33s
- Traction slide: "funktionsfähig und deployed" → "Prototyp-Stadium, mit Testkunden validiert"
- "bereit für zahlende Kunden" → "Ab August 2026 produktiver Betrieb"
- SDK Demo: "produktive Plattform" → "funktionierender Prototyp, mit Testkunden validiert"
- USP: "produktive Engine" → "leistungsfähige Engine"

Until founding in August 2026, all references must indicate prototype/test status.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 01:20:21 +02:00
Benjamin Admin c157e9cbca fix(pitch-deck): TTS language detection, technical FAQ, proper German umlauts + abbreviations
Build pitch-deck / build-push-deploy (push) Failing after 23s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Has been cancelled
TTS Language Bug:
- ChatFAB: detect response language from text content instead of UI language
- German text with umlauts/ß triggers German TTS even when UI is in English

Presenter Script (German TTS pronunciation):
- Add proper umlauts (ä/ö/ü) throughout German text
- Expand abbreviations for clear pronunciation:
  DSGVO → Datenschutz-Grundverordnung
  SAST → Static Application Security Testing
  DAST → Dynamic Application Security Testing
  SBOM → Software Bill of Materials
  VVT → Verarbeitungsverzeichnis
  TOMs → technisch-organisatorische Maßnahmen
  BSI → Bundesamt für Sicherheit in der Informationstechnik
  KMU → kleine und mittlere Unternehmen, etc.

Technical FAQ (12 new entries):
- BGE-M3, RAG, Qdrant, Cross-Encoder, Hybrid Search
- SAST/DAST, SBOM, BSI, Cloud Providers (SysEleven/Hetzner)
- Controls/Prüfaspekte, Policy Engine, VVT/TOMs/DSFA

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 01:18:03 +02:00
Benjamin Admin 9005a05bd7 fix(pitch-deck): version-aware financial model + layout fix + COMPLAI spelling
Build pitch-deck / build-push-deploy (push) Successful in 1m2s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 26s
CI / test-python-voice (push) Successful in 25s
CI / test-bqas (push) Successful in 26s
Critical fix: All financial slides now use the version's preferred scenario
instead of always defaulting to Base Case (1M). This ensures the
Wandeldarlehen version shows its own lean financial plan.

- useFinancialModel: add preferredScenarioId parameter
- PitchDeck: extract default scenario from previewData.fm_scenarios
- Pass preferredScenarioId to all 5 financial slides
- FinancialsSlide layout: remove empty right column, full-width charts
- Remove ScenarioSwitcher + unused slider from FinancialsSlide
- Fix COMPLEI → COMPLAI in presenter script (only TTS pronunciation differs)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 01:02:57 +02:00
Benjamin Admin 98081ae5eb fix(pitch-deck): add loading fallback for Unternehmensentwicklung tile
Build pitch-deck / build-push-deploy (push) Successful in 1m8s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 36s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 34s
Shows "Lade Finanzplan..." when annualKPIs is empty (data not yet loaded)
instead of rendering nothing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 00:47:34 +02:00
Benjamin Admin c99e35438c feat(pitch-deck): rewrite presenter script — emotional tone, correct numbers, all slides
Build pitch-deck / build-push-deploy (push) Successful in 1m13s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 31s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 33s
- Fix "110 Gesetze" → "380+ Regularien" (all occurrences)
- Fix savings: 30k/20k → 13k/9k matching SavingsSlide KMU (55k total, 3.7x ROI)
- Fix "COMPLAI" → "COMPLEI" (pronunciation: like Ei, not AI)
- Remove "Frankreich/France" references
- Remove hardcoded financial projections (now reference computed data)
- Add missing slide scripts: usp, cap-table, customer-savings, annex-strategy,
  annex-finanzplan, annex-glossary, legal-disclaimer
- More emotional, positive, investor-focused tone throughout
- Fix "38 Verordnungen" → "380+ Regularien" in AI pipeline
- Fix module count: "12" → "65 Compliance-Module"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 00:43:30 +02:00
Benjamin Admin 1241a14ea5 Merge branch 'main' of ssh://gitea.meghsakha.com:22222/Benjamin_Boenisch/breakpilot-core
Build pitch-deck / build-push-deploy (push) Successful in 1m16s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 35s
CI / test-python-voice (push) Successful in 28s
CI / test-bqas (push) Successful in 35s
2026-04-17 00:27:17 +02:00
Benjamin Admin 0712d18824 fix(pitch-deck): remove assumption sliders from Financials slide
Investors should not be able to modify business case assumptions.
Questions should be directed to founders via the AI chat agent.
Scenario switcher is kept for viewing different scenarios.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 00:27:01 +02:00
Sharang Parnerkar 71040dcd33 revert: remove <en> tag mixed-language approach from presenter scripts
Build pitch-deck / build-push-deploy (push) Successful in 13s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 33s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 22:37:23 +02:00
Sharang Parnerkar 0923d9b051 fix(presenter): strip <en> tags from displayed subtitle text
Build pitch-deck / build-push-deploy (push) Successful in 1m5s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 33s
CI / test-python-voice (push) Successful in 38s
CI / test-bqas (push) Successful in 29s
Tags are TTS-only markers; display should show plain text.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 22:32:02 +02:00
Sharang Parnerkar 909301a4de feat(pitch-deck): wrap English words with <en> tags for correct TTS pronunciation
Build pitch-deck / build-push-deploy (push) Successful in 1m17s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 34s
CI / test-bqas (push) Successful in 33s
DevSecOps, Onepager, SaaS, deployed, TypeScript, RegTech, OpenAI,
PostgreSQL, NVIDIA, GitLab, Full Compliance GPT, ERPNext — all marked
for English voice synthesis in German presenter script and FAQ.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 22:24:26 +02:00
Sharang Parnerkar d548ce4199 fix(pitch-deck): refresh expired JWT from live DB session on cookie read
Build pitch-deck / build-push-deploy (push) Successful in 1m13s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 37s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 34s
When jwtVerify fails (JWT expired), decode the token without expiry check
to recover sessionId, validate it against the DB, and reissue a fresh 24h
JWT. Fixes investors with old 1h JWTs being locked out on magic link re-click.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 22:18:52 +02:00
Sharang Parnerkar 0188a46afb fix(pitch-deck): fix TTS pronunciation of 25.000+ in presenter scripts
Build pitch-deck / build-push-deploy (push) Successful in 1m10s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 38s
CI / test-python-voice (push) Successful in 38s
CI / test-bqas (push) Successful in 34s
Replace '25.000+' with 'über 25 Tausend' in DE text so Edge TTS speaks
it correctly instead of 'fünfundzwanzig Punkt null null null plus'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 22:14:57 +02:00
Sharang Parnerkar d6be61cdcf fix(pitch-deck): align JWT expiry with session lifetime (24h)
Build pitch-deck / build-push-deploy (push) Successful in 1m10s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 35s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 37s
JWT was set to 1h while the session cookie lived 24h. After 1 hour the
cookie persisted but jwtVerify failed, making /api/auth/me return 401
and the re-click redirect fall through to the already-used token error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 22:09:12 +02:00
Sharang Parnerkar 6e6525a416 fix(pitch-deck): pin presenter TTS to Edge TTS (de-DE-ConradNeural)
Build pitch-deck / build-push-deploy (push) Successful in 1m25s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 44s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 37s
German permanently routes to compliance TTS service (Edge TTS neural
voice, Piper fallback). OVH DE path removed — no env var can flip it
back accidentally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 21:44:12 +02:00
Sharang Parnerkar 6a6b3e8cee feat(pitch-deck): make OVH DE TTS opt-in via OVH_TTS_ENABLED_DE env var
Build pitch-deck / build-push-deploy (push) Successful in 1m25s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 47s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Has been cancelled
Without the flag, German routes to the compliance TTS service which uses
Edge TTS (de-DE-ConradNeural) with Piper as fallback — easier to A/B
between OVH and compliance/Edge TTS without code changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 21:40:32 +02:00
Sharang Parnerkar 09ac22f692 fix(pitch-deck): revert OVH synthesis rate to 16000 Hz
Build pitch-deck / build-push-deploy (push) Successful in 17s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 49s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 36s
OVH honours sample_rate_hz and returns data at exactly the requested
rate, so synthesis and WAV header rates must always match. Decoupled
22050/16000 caused 22050 Hz PCM wrapped in a 16000 Hz header → slow
bloated playback. Both back to 16000 Hz.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 21:37:52 +02:00
Sharang Parnerkar 5a476ac97d fix(pitch-deck): decouple OVH synthesis rate from WAV header rate
Build pitch-deck / build-push-deploy (push) Successful in 1m26s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 49s
CI / test-python-voice (push) Successful in 40s
CI / test-bqas (push) Successful in 37s
OVH uses sample_rate_hz in the request for internal synthesis quality
but always outputs raw PCM at 16000 Hz. Sending 22050 for synthesis
gives better pronunciation; declaring 16000 in the WAV header gives
correct playback speed. Previously both were the same value, forcing
a tradeoff between quality and speed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 21:26:54 +02:00
Sharang Parnerkar 4f2a963834 fix(pitch-deck): set OVH TTS sample rate to 16000 Hz (Riva native)
Build pitch-deck / build-push-deploy (push) Successful in 1m27s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 50s
CI / test-python-voice (push) Successful in 40s
CI / test-bqas (push) Successful in 36s
OVH Riva ignores the sample_rate_hz request param and always returns at
its native 16000 Hz. Declaring a higher rate in the WAV header causes
proportionally slower/deeper playback. 16000 Hz matches the actual output.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 21:16:54 +02:00
Sharang Parnerkar aa7bd79c51 fix(pitch-deck): bump OVH TTS default sample rate to 44100 Hz
Build pitch-deck / build-push-deploy (push) Successful in 1m25s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 45s
CI / test-python-voice (push) Successful in 40s
CI / test-bqas (push) Successful in 35s
22050 Hz declared in WAV header while Riva returns 44100 Hz native PCM
causes playback at half speed — deep, bloated voice. Aligning the
declared rate with the actual output fixes pitch and speed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 21:11:14 +02:00
Sharang Parnerkar 7701a34d7f feat(pitch-deck): redirect to pitch if valid session on magic link re-click
Build pitch-deck / build-push-deploy (push) Successful in 1m25s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 48s
CI / test-bqas (push) Has been cancelled
CI / test-python-voice (push) Has started running
If an investor clicks the magic link again after already being logged in,
check /api/auth/me first — valid session → redirect to / immediately
instead of showing the 'link already used' error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 21:08:26 +02:00
Sharang Parnerkar d35e3f4705 fix(pitch-deck): split email.ts to fix client bundle including nodemailer
Build pitch-deck / build-push-deploy (push) Successful in 1m40s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 45s
CI / test-python-voice (push) Successful in 31s
CI / test-bqas (push) Successful in 43s
Client component (investors/new page) imported DEFAULT_MESSAGE etc. from
lib/email.ts which also top-level initialises nodemailer — webpack tried
to bundle fs/net/dns into the client chunk and failed.

Extract the pure constants + getDefaultGreeting into lib/email-templates.ts
(client-safe), keep nodemailer in lib/email.ts (server-only), update the
page to import from email-templates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 20:56:36 +02:00
Sharang Parnerkar 5d71a371d6 fix(pitch-deck): resolve Docker build failures — nodemailer webpack + jose Edge Runtime
Build pitch-deck / build-push-deploy (push) Failing after 45s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 35s
CI / test-python-voice (push) Successful in 41s
CI / test-bqas (push) Successful in 39s
- Add nodemailer to serverExternalPackages so webpack doesn't try to
  bundle fs/net/dns built-ins (was fatal build error)
- Import jwtVerify from jose/jwt/verify instead of the full jose index
  to avoid pulling in JWE deflate code incompatible with Edge Runtime

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 20:31:34 +02:00
Benjamin Admin f75aef2a4a chore: trigger rebuild
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 38s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 38s
2026-04-16 16:13:14 +02:00
Benjamin Admin 5264528940 style(pitch-deck): highlight Professional tier with silver border on BusinessModel slide
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 38s
CI / test-python-voice (push) Successful in 41s
CI / test-bqas (push) Successful in 37s
Build pitch-deck / build-push-deploy (push) Failing after 47s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 15:36:02 +02:00
Benjamin Admin 084183f3a4 fix(pitch-deck): sync Executive Summary + BusinessModel with compute engine
Build pitch-deck / build-push-deploy (push) Failing after 35s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 35s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 33s
ExecutiveSummarySlide:
- Unternehmensentwicklung: hardcoded table → useFinancialModel + computeAnnualKPIs
  (MA, Kunden, ARR now computed from finanzplan DB for all versions)
- Pricing: aligned with BusinessModelSlide tiers (Starter/Professional/Enterprise)
  Enterprise: 40k → 50k (matching Folie 11)

BusinessModelSlide:
- ACV: hardcoded "15–50k" → computed from summary.final_arr / final_customers
- Gross Margin: hardcoded "> 80%" → computed from lastResult.gross_margin_pct

All financial numbers on all slides now flow from the same compute engine.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 09:02:07 +02:00
Benjamin Admin e05d3e1554 fix(pitch-deck): sync Executive Summary savings with SavingsSlide (Folie 18) KMU data
Build pitch-deck / build-push-deploy (push) Failing after 40s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 35s
CI / test-python-voice (push) Successful in 38s
CI / test-bqas (push) Successful in 37s
Kundenersparnis kachel now matches KMU tier from SavingsSlide:
- Pentests: 30k → 13k (actual savings vs without BreakPilot)
- CE-Beurt. 20k → CE-Risiko 9k
- Audit Mgr. 60k+ → Compliance-Zeit 15k + Audit-Vorb. 9k
- Total: 50-110k → 55k/Jahr (KMU, 3.7x ROI)
- HTML embed: "50.000+ EUR/Jahr" → "55.000 EUR/Jahr (3,7x ROI)"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 08:57:45 +02:00
Benjamin Admin 06f868abeb fix(pitch-deck): replace all hardcoded financial numbers with computed values
Build pitch-deck / build-push-deploy (push) Failing after 40s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 37s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 33s
All financial data now flows from the same compute engine (useFinancialModel).
No more hardcoded numbers in any slide — all values are derived from the
finanzplan database, ensuring consistency across all pitch deck versions.

- FinanzplanSlide: KPI table + charts now use computeAnnualKPIs() from FMResult[]
- BusinessModelSlide: bottom-up calc (customers × ACV = ARR) from compute engine
- AssumptionsSlide: Base case from compute, Bear/Bull scaled from Base
- New helper: lib/finanzplan/annual-kpis.ts for 60-month → 5-year aggregation
- PitchDeck: passes investorId to all financial slides for version-aware data

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 08:48:37 +02:00
Benjamin Admin aed428312f feat(pitch-deck): bilingual email template + invite page with live preview
Build pitch-deck / build-push-deploy (push) Failing after 1m6s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 48s
CI / test-python-voice (push) Successful in 39s
CI / test-bqas (push) Successful in 40s
Email template (email.ts):
- Bilingual: German body + DE/EN legal footer
- Customizable greeting, message body, and closing
- Magic Link explanation box (hardcoded)
- Confidentiality & Disclaimer footer (hardcoded, bilingual)

Invite page (investors/new):
- Name is now required, Company is optional
- Editable fields: greeting, message, closing (with defaults)
- Live email preview panel (right side)
- Shows full email content before sending
- German UI labels

API (invite/route.ts):
- Passes greeting, message, closing to email function

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 08:34:23 +02:00
Benjamin Admin 32851ca9fb feat(pitch-deck): add confidentiality & disclaimer to magic link email
Build pitch-deck / build-push-deploy (push) Successful in 1m16s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 36s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 33s
Adds legal footer to the investor invite email with:
- Confidentiality obligation (3 years, purpose limitation)
- Disclaimer (not an offer, projections only, risk of total loss)
- Jurisdiction: Konstanz, German law

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 08:23:05 +02:00
Benjamin Admin cbee0b534f feat(pitch-deck): TheAsk — 40k/160k/200k tiles, BAFA+L-Bank hint, FAQ, skip CapTable for Wandeldarlehen
Build pitch-deck / build-push-deploy (push) Successful in 1m8s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 36s
CI / test-python-voice (push) Successful in 37s
CI / test-bqas (push) Has been cancelled
- Funding tiles: 40k investor (ab 20%) + 160k L-Bank = 200k, optional 400k row
- Remove Cap Table "Beispielrechnung" from TheAsk slide
- BAFA INVEST title: add hint that L-Bank+BAFA combination must be verified
- Skip CapTable slide entirely for Wandeldarlehen versions (useEffect auto-advance)
- FAQ: add Wandeldarlehen/Pre-Seed BW entry + BAFA+Pre-Seed compatibility entry
- FAQ: fix outdated BAFA INVEST percentage (20% → 15%) in investment-captable entry

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 08:20:29 +02:00
Benjamin Admin 8f44d907a5 feat(pitch-deck): TheAsk slide — Wandeldarlehen version with Pre-Seed BW, Cap Table
Build pitch-deck / build-push-deploy (push) Successful in 1m12s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 35s
CI / test-python-voice (push) Successful in 37s
CI / test-bqas (push) Successful in 36s
- Conditional sections only shown when instrument is "Wandeldarlehen"
- 200k investor ask + 200k L-Bank = 400k total funding visualization
- 3-step explanation: Investment → Conversion → Investor advantage
- Pre-Seed BW / L-Bank co-financing info box
- Cap Table before/after conversion example
- Use of Funds EUR amounts based on 400k total budget
- "1 Mio." version remains completely unaffected

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 07:36:44 +02:00
Benjamin Admin 24ce8ccd20 fix(pitch-deck): TheAsk slide — fix client-side crash
Build pitch-deck / build-push-deploy (push) Successful in 1m12s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 36s
- Replace emoji with Landmark icon
- Add JSON.parse fallback for use_of_funds
- Guard pieData labels and amounts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 00:26:16 +02:00
Benjamin Admin 786993d8ca feat(pitch-deck): add BAFA INVEST program info to The Ask slide
Build pitch-deck / build-push-deploy (push) Successful in 1m12s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 34s
CI / test-bqas (push) Successful in 35s
- 15% tax-free acquisition grant (corrected from 25%)
- 25% exit grant on capital gains
- Up to 40% effective support (entry + exit combined)
- Program extended until 31.12.2026
- Disclaimer to verify current terms at bafa.de

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 00:05:42 +02:00
Benjamin Admin 2b9788bdb0 feat(pitch-deck): add day/night mode toggle to sidebar
Build pitch-deck / build-push-deploy (push) Successful in 1m7s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 40s
CI / test-python-voice (push) Successful in 34s
CI / test-bqas (push) Successful in 34s
- Theme toggle button below language toggle
- Uses existing theme-light CSS class from globals.css
- Moon/Sun icons with Nacht/Tag labels (DE) or Dark/Light (EN)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 23:58:48 +02:00
Benjamin Admin 91b5ce990f fix(pitch-deck): remove Kernmarkt label, pricing from product, bigger disclaimer
Build pitch-deck / build-push-deploy (push) Successful in 1m6s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 31s
- BusinessModel: remove "Kernmarkt" text, stronger highlight (shadow+border)
- Product: remove Pricing kachel, split Deployment into 2 side-by-side
  cards (Cloud + Privacy Hardware), larger text
- Executive Summary: disclaimer font size increased (9px→11px, 10px→12px)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 23:50:49 +02:00
Benjamin Admin 936b4ccc51 fix(pitch-deck): glossary — align abbreviations with descriptions (items-baseline)
Build pitch-deck / build-push-deploy (push) Successful in 1m11s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 35s
CI / test-python-voice (push) Successful in 34s
CI / test-bqas (push) Successful in 32s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 23:38:00 +02:00
Benjamin Admin 9e3f15ce4e fix(pitch-deck): increase font sizes on slides 8, 11, 18, 25, 27
Build pitch-deck / build-push-deploy (push) Has been cancelled
CI / go-lint (push) Has been cancelled
CI / python-lint (push) Has been cancelled
CI / nodejs-lint (push) Has been cancelled
CI / test-go-consent (push) Has been cancelled
CI / test-python-voice (push) Has been cancelled
CI / test-bqas (push) Has been cancelled
- All text-[10px] → text-xs (12px)
- All text-[9px] → text-[11px]
- All text-[8px] → text-[10px]
- Affected: BusinessModel, Product, Savings, Strategy slides
- Engineering: revert LoC to 481K (compliance SDK only, not all repos)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 23:34:59 +02:00
Benjamin Admin 7523f47468 fix(pitch-deck): engineering slide — sync numbers with real data
Build pitch-deck / build-push-deploy (push) Successful in 1m4s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 33s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 30s
- 481K LoC → 960K+ (actual count across 3 repos)
- 10 Services → 320 Dokumente im RAG (aligned with Slide 7)
- 48+ SDK-Module → 70K+ Compliance Controls (from DB)
- 5 Infra → 12 Produkt-Module (aligned with Slide 8)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 23:26:26 +02:00
Benjamin Admin 6de8b33dd1 fix(pitch-deck): regulatory slide — white headers for requirements + how we help
Build pitch-deck / build-push-deploy (push) Has been cancelled
CI / python-lint (push) Has been cancelled
CI / nodejs-lint (push) Has been cancelled
CI / go-lint (push) Has been cancelled
CI / test-go-consent (push) Has been cancelled
CI / test-python-voice (push) Has been cancelled
CI / test-bqas (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 23:23:07 +02:00
Benjamin Admin 79c01c85fa fix(pitch-deck): realistic savings — credible ROI for investors
Build pitch-deck / build-push-deploy (push) Successful in 1m11s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 29s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 33s
- Ext. DSB: 6k→3k (halved, not eliminated)
- Compliance docs: 0→2k (reduced effort, not zero)
- Personnel: "~2/8/40 MA savings" → "50% more productive compliance time"
  (realistic productivity gain, not full headcount elimination)
- ROIs now credible: KMU 3.7x, Mittelstand 6.4x, Konzern 15.6x
  (was 11x/21x/62x — too aggressive for investors)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 23:17:43 +02:00
Benjamin Admin 735cab2018 fix(pitch-deck): add pulse animation to MarketSlide inactive tabs
Build pitch-deck / build-push-deploy (push) Successful in 1m11s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 33s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 23:10:22 +02:00
Benjamin Admin b4e8b74afb fix(pitch-deck): center KPI card labels and values
Build pitch-deck / build-push-deploy (push) Has been cancelled
CI / python-lint (push) Has been cancelled
CI / go-lint (push) Has been cancelled
CI / nodejs-lint (push) Has been cancelled
CI / test-python-voice (push) Has been cancelled
CI / test-go-consent (push) Has been cancelled
CI / test-bqas (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 23:05:46 +02:00
Benjamin Admin 4b06933576 fix(pitch-deck): sync Executive Summary modules with Slide 8
Build pitch-deck / build-push-deploy (push) Successful in 1m17s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 36s
- Cookie-Generator → Tender Matching (RFQ gegen Codebase)
- Integration → AI Act Compliance (UCCA, Betriebsrat)
- Text: Integration in Kundenprozesse → AI Act + Tender Matching

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 22:59:41 +02:00
Benjamin Admin 89a6b90ca6 fix(pitch-deck): remaining umlauts + COMPLAI consistency
Build pitch-deck / build-push-deploy (push) Successful in 1m18s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 36s
CI / test-python-voice (push) Successful in 37s
CI / test-bqas (push) Successful in 38s
- 17 more umlaut fixes (Konformitätsbewertung, Löschkonzept,
  Portabilität, Regelmäßige, etc.) across 6 files
- ComplAI → COMPLAI in all string contexts for consistency
- BrandName component used for JSX rendering (gradient AI)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 22:54:09 +02:00
Benjamin Admin f9b9cf0383 feat(pitch-deck): business model redesign + umlauts fix + tab animations
Build pitch-deck / build-push-deploy (push) Successful in 1m12s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 37s
CI / test-python-voice (push) Successful in 37s
CI / test-bqas (push) Successful in 36s
Business Model slide completely rewritten:
- Left: 3 pricing tiers (Starter/Professional/Enterprise)
- Right: Unit Economics (ACV, Gross Margin, NRR, Payback)
- Bottom-up sizing: 1,200 customers × 8,400 ACV = 10M ARR
- Land & Expand arrow visualization

Umlauts: 75+ ae/oe/ue → ä/ö/ü replacements across 10 slide files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 22:42:06 +02:00
Benjamin Admin 2de4d03d81 Merge branch 'main' of ssh://gitea.meghsakha.com:22222/Benjamin_Boenisch/breakpilot-core
Build pitch-deck / build-push-deploy (push) Successful in 1m9s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 37s
CI / test-bqas (push) Successful in 36s
2026-04-15 22:26:57 +02:00
Benjamin Admin d2c2fd92cc feat(pitch-deck): tab pulse animation + BrandName in regulatory/competition
- Inactive tabs pulse gently (animate-[pulse_3s]) on:
  Competition, AIPipeline, Financials, Regulatory slides
- RegulatorySlide: "Wie ComplAI hilft" → BrandName component
- CompetitionSlide: "ComplAI" label → BrandName component

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 22:26:05 +02:00
Sharang Parnerkar 032df7f401 fix(pitch-deck): coerce pg NUMERIC to Number globally — fixes Finanzen crash
Build pitch-deck / build-push-deploy (push) Successful in 1m14s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 39s
CI / test-python-voice (push) Successful in 38s
CI / test-bqas (push) Successful in 29s
The Finanzen slide crashed on mount with "a.toFixed is not a function".
Traced to UnitEconomicsCards.tsx:59 calling ltvCacRatio.toFixed(1),
where ltvCacRatio arrives as a string.

Root cause: the cached path in /api/financial-model/compute returns raw
rows from pitch_fm_results. node-postgres returns NUMERIC / DECIMAL
columns as strings by default, so lastResult.ltv_cac_ratio (and every
other *_eur / *_pct / *_ratio field) flows through the app as a string.
Arithmetic-heavy code paths survived on accidental string-coerce (`-`,
`/`, `*`), but direct method calls like .toFixed() don't coerce, which
is why Unit Economics was the visible crash site.

Fix at the boundary: register a single types.setTypeParser(NUMERIC, …)
on the pg Pool so every query returns real numbers. All our NUMERIC
values fit well inside Number.MAX_SAFE_INTEGER, so parseFloat is safe.

One-line change, no component-level defensive coercions needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 22:19:57 +02:00
Benjamin Admin 474f09ce88 fix(pitch-deck): USP compliance text position + regulatory KPI labels
Build pitch-deck / build-push-deploy (push) Successful in 1m17s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 35s
CI / test-python-voice (push) Successful in 39s
CI / test-bqas (push) Successful in 38s
- USP: Compliance block shifted right (left-7 → left-12)
- Regulatory: KPI labels more descriptive:
  Horizontal → "Gelten für alle Branchen"
  Sektorspezifisch → "Branchenspezifische Gesetze"
  Industriesektoren → "Abgedeckte Branchen"
  Dokumente column → "Gesetze gesamt"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 21:21:39 +02:00
Benjamin Admin e920dd1b3f feat(pitch-deck): savings slide — aggressive personnel savings, fix terminology
Build pitch-deck / build-push-deploy (push) Successful in 1m16s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 35s
CI / test-python-voice (push) Successful in 37s
CI / test-bqas (push) Successful in 38s
- Apps → Anwendungen/Softwareapplikationen
- Remove "Shift-Left" — replaced with KI-Automatisierung Personalersparnis
- KMU (25 MA): ~2 MA Ersparnis = 120k€/Jahr → ROI 11x (was 3.5x)
- Mittelstand (100 MA): ~8 MA Ersparnis = 480k€ → ROI 21x (was 7.5x)
- Konzern (500+ MA): ~40 MA Ersparnis = 2.4M€ → ROI 62x (was 20.8x)
- Linear scaling of personnel savings across tiers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 21:15:01 +02:00
Benjamin Admin 5ddf8bbc3c fix(pitch-deck): architecture + GTM corrections
Build pitch-deck / build-push-deploy (push) Successful in 1m17s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 30s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Has been cancelled
Architecture:
- "Daten verlassen nie das Unternehmen" → "nie BSI-zertifizierte Server in DE"
- "Keine Cloud-Abhängigkeit" → "100% EU-Cloud · Keine US-Anbieter"
- Mac Mini/Studio: remove GB/model specs, mark as (geplant, optional)

GTM:
- Phase 1 focus: Maschinenbau, Automotive, Elektro (was Healthcare, Finance)
- ICP: Produzierende Industrie (was Regulierte Branche)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 21:11:46 +02:00
Benjamin Admin 14cde7b3ee feat(pitch-deck): disclaimer 2 founders, glossary +12 terms, SDK demo + strategy fixes
Build pitch-deck / build-push-deploy (push) Successful in 1m16s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 36s
CI / test-python-voice (push) Successful in 38s
CI / test-bqas (push) Successful in 38s
Disclaimer: 2 founders (Bodman + Engen), all singular→plural
Glossary: +FISA 702, Cloud Act, BDSG, BSI, RAG, LLM, UCCA, FRIA,
  SDK, OWASP, NIST, ENISA, CE, RFQ (new Technology category)
SDK Demo: Müller Maschinenbau → Muster Maschinenbau (example customer)
Strategy: CANCOM/Bechtle disclaimer (planned, not yet contacted)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 21:03:48 +02:00
Benjamin Admin 581162cdb8 fix(pitch-deck): footer readability + finanzplan import endpoint
Build pitch-deck / build-push-deploy (push) Successful in 1m9s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 35s
CI / test-python-voice (push) Successful in 37s
CI / test-bqas (push) Successful in 38s
- Regulatory landscape footer: text-xs text-white/50 (was text-[9px] text-white/20)
- New POST /api/admin/import-fp endpoint to import fp_* data from JSON dump

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 19:43:08 +02:00
Benjamin Admin dc27fc5500 feat(pitch-deck): regulatory landscape based on real rag-documents.json
Build pitch-deck / build-push-deploy (push) Successful in 1m10s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 37s
Complete rewrite of Slide 7:
- 10 real VDMA/VDA/BDI industry sectors (was 11 mixed categories)
- 7 key EU regulations as columns (DSGVO, AI Act, NIS2, CRA,
  Maschinenverordnung, Data Act, Batterieverordnung)
- Actual document counts per industry (244 horizontal + sector-specific)
- Last column: total applicable documents (not regulation count)
- KPIs: 320 docs, 244 horizontal, 65 sector-specific, 10 sectors
- Footer explains horizontal vs sector-specific logic
- Subtitle: 320 Dokumente im RAG — 10 Industriesektoren

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 19:21:07 +02:00
Benjamin Admin 51649c874b Merge branch 'main' of ssh://gitea.meghsakha.com:22222/Benjamin_Boenisch/breakpilot-core
Build pitch-deck / build-push-deploy (push) Successful in 1m10s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 35s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 33s
2026-04-15 19:07:27 +02:00
Benjamin Admin 4d7836540a feat(pitch-deck): add admin migration endpoint for finanzplan tables
POST /api/admin/migrate creates all fp_* tables on production DB.
Admin-only, creates tables with IF NOT EXISTS for safe re-runs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 19:06:32 +02:00
Sharang Parnerkar 3419e18d7f feat(pitch-deck): add Sharang Parnerkar photo
Build pitch-deck / build-push-deploy (push) Successful in 1m12s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 31s
CI / test-python-voice (push) Successful in 30s
CI / test-bqas (push) Successful in 34s
GitHub avatar (github.com/mighty840) saved as /team/sharang-parnerkar.jpg.
Team-data JSON for both draft versions (Wandeldarlehen and The Ask 1 Mio)
was updated out-of-band via the admin API:

- Bio lengthened (~640 chars DE/EN) to match Benjamin's depth — now
  covers the ETO tenure (3→60 org scale), ETOPay, ViviSwap/MiCA,
  enterprise AI on AWS/Azure/SysEleven, embedded Rust work, and the
  ferrite-sdk open-source project.
- photo_url switched from empty to /team/sharang-parnerkar.jpg.
- Expertise tags unchanged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 18:50:04 +02:00
Benjamin Admin a9b71b9d23 Merge branch 'main' of ssh://gitea.meghsakha.com:22222/Benjamin_Boenisch/breakpilot-core
Build pitch-deck / build-push-deploy (push) Successful in 1m7s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 31s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 34s
2026-04-15 18:45:08 +02:00
Benjamin Admin e8a18c0025 perf(pitch-deck): fix slow financial slides — cached results + batch insert
- Compute endpoint now returns cached results if available (single SELECT
  instead of DELETE + 60 INSERTs)
- When recompute is needed, batch all 60 rows into a single INSERT
- Reduces DB calls from 61 to 2 (cached) or 3 (recompute)
- Fixes timeout/blank financial slides for investors

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 18:43:05 +02:00
Sharang Parnerkar 3e9a988aaf perf(pitch-deck): smooth SDK demo carousel — no blank frames, parallel preload
Build pitch-deck / build-push-deploy (push) Successful in 1m14s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 34s
CI / test-bqas (push) Successful in 31s
The SDK Live Demo was janky: AnimatePresence mode="wait" unmounted the
current Image before mounting the next, so every advance forced a cold
fetch and left an empty black frame until the new image decoded. Only
the first three screenshots had priority; the rest fetched lazily, so
the first pass through the carousel repeatedly stalled.

Replaces the single swap-in/swap-out Image with a stack of 23 images
layered in an aspect-[1920/1080] container. Cross-fades are now pure
CSS opacity on always-mounted nodes, so there is no unmount and no gap.

Key details:
- priority on the first 3 (triggers <link rel="preload">); loading=eager
  on the remaining 20 so the browser starts all fetches at mount rather
  than deferring via IntersectionObserver.
- sizes="(max-width: 1024px) 100vw, 1024px" lets next/image serve the
  actual displayed resolution instead of the 1920 hint — fewer bytes,
  faster first paint.
- Load-gated reveal: a new `shown` state trails `current` until the
  target image fires onLoadingComplete. If the user clicks ahead of
  the network, the previous loaded screenshot stays visible — no more
  black flashes before images arrive.

Second pass through the carousel is instant (images are in-cache).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 18:35:55 +02:00
Sharang Parnerkar 01f05e4399 feat(pitch-deck): route DE presenter TTS through OVH via LiteLLM passthrough
Adds an OVH-backed branch to /api/presenter/tts so the German presenter
narration is synthesized by OVH AI Endpoints' nvr-tts-de-de (NVIDIA Riva)
reached through the LiteLLM passthrough at /tts-ovh/audio/*, which
injects the OVH API token server-side.

- DE requests now hit ${LITELLM_URL}/tts-ovh/audio/v1/tts/text_to_audio
  with the documented body shape (encoding=1, language_code=de-DE,
  voice_name=German-DE-Male-1, sample_rate_hz=22050) and return the
  audio/wav bytes upstream serves (confirmed RIFF-framed in a smoke test).
- EN continues to hit compliance-tts-service until OVH_TTS_URL_EN is set,
  making the eventual EN switch a single env flip.
- OVH and voice/url/sample-rate parameters are env-overridable
  (OVH_TTS_URL_DE, OVH_TTS_VOICE_DE, OVH_TTS_SAMPLE_RATE,
  OVH_TTS_URL_EN, OVH_TTS_VOICE_EN) so retuning doesn't need a redeploy.
- Defensive: OVH failures surface as 502 (no silent fallback) so upstream
  issues are visible during this test rollout.
- wrapPcmAsWav() helper is kept as a safety net in case OVH ever returns
  bare PCM instead of a full WAV.

Adds X-TTS-Source response header (ovh | compliance) to make
provenance observable from DevTools.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 18:35:55 +02:00
Benjamin Admin 7c17e484c1 fix(pitch-deck): add /team to public paths for team photo access
Build pitch-deck / build-push-deploy (push) Successful in 1m15s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 36s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 33s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 18:23:52 +02:00
Benjamin Admin ea39418738 Merge branch 'main' of ssh://gitea.meghsakha.com:22222/Benjamin_Boenisch/breakpilot-core
Build pitch-deck / build-push-deploy (push) Has been cancelled
CI / go-lint (push) Has been cancelled
CI / python-lint (push) Has been cancelled
CI / nodejs-lint (push) Has been cancelled
CI / test-go-consent (push) Has been cancelled
CI / test-python-voice (push) Has been cancelled
CI / test-bqas (push) Has been cancelled
2026-04-15 18:21:15 +02:00
Benjamin Admin 7f88ed0ed2 feat(pitch-deck): add Benjamin Boenisch photo + update team data
- Photo extracted from CV and placed in public/team/
- Team data updated via MCP (both versions):
  - Bio: 18+ years industry/strategy, SVP at ETO GRUPPE,
    60 employees, M&A, 11 patents, VDMA/CyberLAGO memberships
  - Role: CEO & Gründer (was CEO & Co-Founder)
  - Expertise tags: Strategie & M&A, DSGVO/AI Act/CRA,
    IoT & Embedded, Web3 & Blockchain, 11 Patente
  - photo_url set to /team/benjamin-boenisch.png

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 18:20:23 +02:00
Sharang Parnerkar 44659a9dd7 fix(pitch-deck): serve /screenshots/* past the auth middleware
Build pitch-deck / build-push-deploy (push) Has been cancelled
CI / go-lint (push) Has been cancelled
CI / python-lint (push) Has been cancelled
CI / nodejs-lint (push) Has been cancelled
CI / test-go-consent (push) Has been cancelled
CI / test-python-voice (push) Has been cancelled
CI / test-bqas (push) Has been cancelled
The SDK Live Demo slide renders screenshots via next/image from
/public/screenshots/*.png. Because /screenshots was not on the
PUBLIC_PATHS list, every request was 307-redirected to /auth, and the
next/image optimizer responded with
  HTTP 400 "The requested resource isn't a valid image."
leaving the slide with empty dark frames (surfaced in the pitch preview).

next/image also bypasses middleware itself (see the matcher), but the
server-side fetch it performs for the source URL does hit middleware
and carries no investor cookie, so whitelisting the path is required
even for authenticated viewers.

These PNGs are public marketing assets — there's no reason to gate them.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 18:20:16 +02:00
Sharang Parnerkar 87d7da0198 fix(pitch-deck): point SDK demo URL mockup at admin-dev.breakpilot.ai
Build pitch-deck / build-push-deploy (push) Successful in 1m9s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 30s
The SDK live-demo slide renders a fake browser URL bar to frame each
screenshot. It used admin.breakpilot.ai, but the actual demo instance
investors should be able to reach lives on admin-dev.breakpilot.ai.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 18:09:04 +02:00
Benjamin Admin 9675c1f896 Merge branch 'main' of ssh://gitea.meghsakha.com:22222/Benjamin_Boenisch/breakpilot-core
Build pitch-deck / build-push-deploy (push) Successful in 1m11s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 30s
CI / test-bqas (push) Successful in 31s
2026-04-15 18:00:45 +02:00
Benjamin Admin 9736476a0c feat(pitch-deck): legal disclaimer slide + projection footer on financial slides
New DisclaimerSlide (last slide):
- Full liability disclaimer (German/English)
- Confidentiality clause (purpose limitation, 3yr duration, Konstanz jurisdiction)
- Status as private individual in founding preparation

ProjectionFooter component on 4 financial slides:
- FinancialsSlide, TheAskSlide, FinanzplanSlide, CapTableSlide
- "Alle Finanzdaten sind Planzahlen" disclaimer

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 18:00:08 +02:00
Sharang Parnerkar 03d420c984 feat(pitch-deck): self-service magic-link reissue on /auth
Build pitch-deck / build-push-deploy (push) Successful in 1m5s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 31s
CI / test-python-voice (push) Successful in 31s
CI / test-bqas (push) Successful in 31s
Investors who lost their session or whose invite token was already used
can now enter their email on /auth to receive a fresh access link,
without needing a manual re-invite from an admin.

- New /api/auth/request-link endpoint looks up the investor by email,
  issues a new pitch_magic_links row, and emails the link via the
  existing sendMagicLinkEmail path. Response is generic regardless of
  whether the email exists (enumeration resistance) and silently no-ops
  for revoked investors.
- Rate-limited both per-IP (authVerify preset) and per-email (magicLink
  preset, 3/hour — same ceiling as admin-invite/resend).
- /auth page now renders an email form; submits to the new endpoint and
  shows a generic "if invited, link sent" confirmation.
- Route-level tests cover validation, normalization, unknown email,
  revoked investor, and both rate-limit paths.
- End-to-end regression test wires request-link + verify against an
  in-memory fake DB and asserts the full flow: original invite used →
  replay rejected → email submission → fresh link → verify succeeds.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 17:06:12 +02:00
Benjamin Admin 6b52719079 feat(pitch-deck): rename Traction → Meilensteine, update milestones data
Build pitch-deck / build-push-deploy (push) Successful in 1m10s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 36s
- i18n: Traction & Meilensteine → Meilensteine / Milestones
- slideNames updated (DE + EN)
- Chat display name updated
- Milestones data replaced via MCP (both versions):
  13 milestones chronologically: domains, DPMA, IHK, prototype,
  pilot customers, RAG pipeline, EUIPO, L-Bank, Gründerzuschuss,
  GmbH founding, onboarding, App Store, distribution
- Metrics updated: 385 docs, 25k controls, 12 modules, etc.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 16:20:47 +02:00
Benjamin Admin a5b7d62969 fix(pitch-deck): USP cards wider (290px), circle larger (440px), more height
Build pitch-deck / build-push-deploy (push) Successful in 1m11s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 30s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 26s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 15:49:28 +02:00
Benjamin Admin ef9e3699b2 fix(pitch-deck): USP cards overlap — increase container height to 520px
Build pitch-deck / build-push-deploy (push) Successful in 1m8s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 31s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 27s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 15:06:33 +02:00
Benjamin Admin 440367b69d feat(pitch-deck): USP font sizes match Solution slide, product modules updated
Build pitch-deck / build-push-deploy (push) Successful in 1m4s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 31s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 30s
USP slide:
- Title/subtitle same as Solution (text-4xl/text-lg)
- Card titles: text-base font-bold (was text-xs)
- Card descriptions: text-sm text-white/50 (was text-[10px])
- Circle text: text-sm (was text-[11px]/text-[9px])
- Cards 240px wide with GlassCard wrapper

Product slide:
- "Integration in Kundenprozesse" → "AI Act Compliance" (UCCA, Betriebsrat)
- "Cookie-Generator" → "Tender Matching" (RFQ gegen Codebase)
- Remove "FR" badge from deployment options

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 14:45:14 +02:00
Benjamin Admin 801a5a43f5 feat(pitch-deck): USP slide — larger circle, title back, infinity hub
Build pitch-deck / build-push-deploy (push) Successful in 1m6s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 34s
- USP as slide title (GradientText) above
- Circle doubled to 380px with spinning ring
- Infinity symbol (∞) in center hub instead of text
- Compliance left, Code right inside circle — larger font
- 4 cards in corners (220px wide, larger text, ~5 lines each)
- Cards spread to corners (top/bottom, left/right)
- Dashed SVG lines connecting circle to cards

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 14:33:28 +02:00
Benjamin Admin 9c23068a4f feat(pitch-deck): USP slide — large circle with cards on sides
Build pitch-deck / build-push-deploy (push) Successful in 1m7s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 35s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 31s
- Large spinning circle (320px) with USP hub in center
- Compliance items left, Code items right inside circle
- 4 arrows pointing outward to capability cards
- 2 cards left (RFQ, Bidirectional), 2 cards right (Process, Continuous)
- Longer descriptions (~5 lines per card)
- Grid layout: cards | circle | cards

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 14:26:59 +02:00
Benjamin Admin d359b7b734 fix(pitch-deck): HowItWorks line behind icons, remove France refs, SOM label
Build pitch-deck / build-push-deploy (push) Successful in 1m6s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 31s
CI / test-python-voice (push) Successful in 30s
CI / test-bqas (push) Successful in 30s
- Connection line: starts/ends between icons, opaque icon background
- Remove all "oder Frankreich/or France/oder FR/or FR" references
- Market subtitle: remove "Der Maschinenbau"
- SOM label: add "(nur Maschinen- und Anlagenbauer als Kernmarkt)"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 13:46:21 +02:00
Benjamin Admin bd37ff807e fix(pitch-deck): USP slide complete redesign — grid layout
Build pitch-deck / build-push-deploy (push) Successful in 1m7s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 30s
CI / test-python-voice (push) Successful in 31s
CI / test-bqas (push) Successful in 31s
Replace broken absolute positioning with clean grid layout:
- Top: Compliance card | BreakPilot hub (spinning) | Code card
- Arrows + sync labels between cards
- Bottom: 4 capability cards in a row
- No more floating text, no overlapping elements

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 13:25:03 +02:00
Benjamin Admin 40d2342086 fix(pitch-deck): fix JSX syntax error in USPSlide corner cards
Build pitch-deck / build-push-deploy (push) Successful in 1m3s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 30s
CI / test-python-voice (push) Successful in 29s
CI / test-bqas (push) Successful in 27s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 13:14:03 +02:00
Benjamin Admin adf3bf8301 feat(pitch-deck): USP slide redesign + add to sidebar
Build pitch-deck / build-push-deploy (push) Failing after 20s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 31s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 33s
- USP added to slideNames (DE+EN) and chat display names
- Circular layout: BreakPilot hub center, rotating ring,
  Compliance & Code sections inside circle
- 4 capability cards in corners connected by dashed lines
- Removed variant toggle (kept variant A design)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 13:04:06 +02:00
Benjamin Admin 1b5ccd4dec feat(pitch-deck): solution text fixes + USP bridge 3 variants
Build pitch-deck / build-push-deploy (push) Successful in 1m5s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 36s
CI / test-python-voice (push) Successful in 38s
CI / test-bqas (push) Successful in 32s
- Solution: 30k → 15k+ EUR per year per application
- Solution: DE oder FR → Deutschland
- USP title: Unser USP → USP
- USP bridge: 3 switchable variants (A: circular loop,
  B: infinity loop, C: hexagonal hub) with toggle buttons

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 12:57:30 +02:00
Benjamin Admin b5d8f9aed3 feat(pitch-deck): add USP slide + update cover and problem texts
Build pitch-deck / build-push-deploy (push) Successful in 1m8s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 30s
CI / test-python-voice (push) Successful in 29s
CI / test-bqas (push) Successful in 28s
- Cover: remove "für den Maschinenbau" from tagline
- Problem subtitle: Maschinenbauer → Deutsche und europäische Unternehmen
- New USP slide after Solution: bridge between compliance docs/audits
  and actual code implementation — RFQ verification, bidirectional sync,
  automated process compliance, continuous instead of annual checks

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 12:44:32 +02:00
Benjamin Admin c8171b0a1e chore(pitch-deck): trigger rebuild 2
Build pitch-deck / build-push-deploy (push) Successful in 1m22s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 36s
CI / test-python-voice (push) Successful in 28s
CI / test-bqas (push) Successful in 28s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 11:05:40 +02:00
Benjamin Admin 7e15ef3725 chore(pitch-deck): trigger rebuild for i18n Problem slide changes
Build pitch-deck / build-push-deploy (push) Failing after 24s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Failing after 9s
CI / test-python-voice (push) Failing after 11s
CI / test-bqas (push) Failing after 10s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 08:54:03 +02:00
Benjamin Admin e3a3802f5b chore(pitch-deck): trigger rebuild for i18n changes
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Failing after 8s
CI / test-python-voice (push) Failing after 9s
CI / test-bqas (push) Failing after 10s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 08:53:17 +02:00
Benjamin Admin 93e319e9fb feat(pitch-deck): rewrite Problem slide cards for investors
Build pitch-deck / build-push-deploy (push) Failing after 8s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Failing after 8s
CI / test-python-voice (push) Failing after 5s
CI / test-bqas (push) Failing after 5s
- Card 1 (KI-Dilemma): clearer framing of sovereignty vs competitiveness
- Card 2: Patriots Act → Patriot Act + FISA 702, Schrems II reference
- Card 3: 50.000+ EUR → Nicht tragbar / Unsustainable, focus on
  AI Act, NIS2, CRA since 2024, competitive disadvantage vs US/Asia,
  supply chain costs, geopolitical pressure
- Quote updated: Maschinenbauer → Produzierende Unternehmen

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 07:59:59 +02:00
Benjamin Admin 6626d2a8f9 fix(pitch-deck): fix ReferenceError in ChatFAB breaking 2nd message
Build pitch-deck / build-push-deploy (push) Successful in 1m4s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 28s
CI / test-bqas (push) Successful in 28s
faqMatch (undefined) → faqMatches[0]. The undefined variable caused
a ReferenceError after streaming completed, which the catch block
turned into "Verbindung fehlgeschlagen" for every subsequent message.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 00:53:42 +02:00
Benjamin Admin 3dbc470158 feat: DSFA Generator — FISA 702 Risiken bei US-Cloud-Providern
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 26s
CI / test-python-voice (push) Successful in 29s
CI / test-bqas (push) Successful in 30s
Erkennt automatisch US-Provider (AWS, Azure, Google, Microsoft, OpenAI,
Anthropic, Oracle, Amazon) und fuegt 3 Drittland-Risiken hinzu:
- FISA 702 Zugriff nicht ausschliessbar
- EU-Serverstandort schuetzt nicht gegen US-Rechtszugriff
- Fehlende Rechtsbehelfe fuer EU-Betroffene

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 00:47:21 +02:00
Benjamin Admin e5d0386cfb feat(pitch-deck): add FISA 702 FAQ entries for investor agent
Build pitch-deck / build-push-deploy (push) Successful in 1m1s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 30s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 26s
5 new FAQ entries covering:
- FISA 702 basics (PRISM, Upstream, Schrems II)
- EU cloud region myth (extraterritorial US law)
- DSFA contradiction (risk acceptance vs risk elimination)
- Market opportunity (structural independence)
- BreakPilot architecture (BSI, SysEleven, Hetzner)

Also: middleware fix to allow admin sessions on investor routes
(enables chat in preview mode)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 00:27:47 +02:00
Benjamin Admin ff071af2a0 fix(pitch-deck): allow admin sessions to access investor routes
Build pitch-deck / build-push-deploy (push) Successful in 1m3s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 30s
CI / test-python-voice (push) Successful in 30s
CI / test-bqas (push) Successful in 34s
Admins in preview mode can now use /api/chat and other investor
endpoints without needing a separate investor login.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 00:13:13 +02:00
Benjamin Admin fcdcbc51e3 fix(pitch-deck): regulatory matrix header positioning
Build pitch-deck / build-push-deploy (push) Successful in 1m12s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 31s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 30s
- Regulatorien + Branche moved to top header row
- Branche: white/70 instead of white/30 for readability
- Regulatorien: indigo color instead of grey

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 23:59:53 +02:00
Benjamin Admin 7b8f8d4b5a fix(pitch-deck): regulatory matrix — remove legend, stagger headers
Build pitch-deck / build-push-deploy (push) Successful in 1m1s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 30s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 29s
- Remove colored dot legend row (redundant with column headers)
- Stagger column headers on 2 rows (odd/even) to save space
- Last column: Reg. → Regulatorien

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 23:54:59 +02:00
Benjamin Admin f385c612f5 fix(pitch-deck): regulatory matrix header alignment + labels
Build pitch-deck / build-push-deploy (push) Successful in 1m4s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 30s
CI / test-bqas (push) Successful in 32s
- Column headers: centered text labels instead of icons
- Remove colored dots from headers
- Last column: # → Reg. (Regulierungen)
- Consistent column width for last column

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 23:45:24 +02:00
Benjamin Admin 9166d9dade fix(pitch-deck): resolve merge conflict in AIPipelineSlide — keep updated version
Build pitch-deck / build-push-deploy (push) Successful in 1m0s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 30s
CI / test-bqas (push) Successful in 31s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 17:42:13 +02:00
Benjamin Admin 7ae5bc0fd5 feat(pitch-deck): overhaul AI Pipeline slide with real data
- Hero stats: 75+ sources, 70k+ controls, 47k+ obligations
- RAG tab: source categories with investor-friendly explanations
  (why court rulings matter, why frameworks define state of art)
- Remove inflated numbers (was 110+ regulations, now accurate 75+)
- Quality tab: continuous expansion, cross-regulation mapping
- Remove NiBiS/education references (irrelevant for compliance)
- All numbers verified against production database

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 17:40:27 +02:00
Sharang Parnerkar 242ed1101e style(team): tighter card layout — equal height, equity pill, GitHub/LinkedIn detection
Build pitch-deck / build-push-deploy (push) Successful in 1m11s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 33s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 31s
- grid items-stretch so cards match height
- Smaller avatar (16->64px) to free vertical space
- Equity moved to a top-right pill (compact); decimals collapsed via equityDisplay()
- Profile link icon auto-detects GitHub vs LinkedIn vs generic
- Expertise tags get their own divider strip at card bottom — cleaner hierarchy
- Card background lightened from 0.08 to 0.04 with subtle hover border

Bio text itself shortened on the data side (both draft versions via admin API).
2026-04-14 16:25:37 +02:00
Sharang Parnerkar 8b2e9ac328 content(pitch-deck): tidy slide text — remove OVH, generalize issue tracker, add live support, Mac Studio option
Build pitch-deck / build-push-deploy (push) Successful in 1m4s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 34s
CI / test-bqas (push) Successful in 32s
Solution slide:
- Continuous Code Security: "Jira tickets" -> "tickets in the issue tracker of your choice"
- German Cloud / Full Integration: removed OVH (now "BSI cloud DE or FR"),
  removed "AI task creation from audio", added "Live support via Jitsi (video) and Matrix (chat)",
  "Mac Mini" -> "Mac Mini/Studio"

Products / Modular toolkit slide:
- Regional bubble: "OVH FR" -> "FR"

How It Works:
- Cloud step: removed OVH and "pre-configured Mac Mini" mentions

Engineering deep dive:
- "Docker Containers" stat -> "Services"; "Coolify -> Hetzner" -> "orca -> Hetzner"
- "Dockerfiles / Fully containerized" stat -> "Infra Components / orca (Rust) + infisical + pg + qdrant"
- devopsStack: Coolify -> orca (Rust), Docker Compose -> Private Registry (registry.meghsakha.com),
  HashiCorp Vault -> Infisical, EU-Cloud list drops OVH
- Service Architecture Infrastructure section: add orca (Rust), Infisical, Private Registry
- Footer note drops OVH

Chat / Presenter (consistency):
- chat/route.ts system prompt: OVH removed, Jira-Integration -> Issue-Tracker-Integration
- presenter-faq.ts + presenter-script.ts: OVH references removed across all answers,
  Jira mentioned alongside GitLab/Linear/Gitea as examples, Mac Mini -> Mac Mini/Studio

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 16:14:40 +02:00
Benjamin Admin 084d09e9bd fix(pitch-deck): revert banner test text back to Draft
Build pitch-deck / build-push-deploy (push) Successful in 13s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 31s
CI / test-bqas (push) Successful in 31s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 15:32:46 +02:00
Benjamin Admin 646143ce5a Merge branch 'main' of ssh://gitea.meghsakha.com:22222/Benjamin_Boenisch/breakpilot-core
Build pitch-deck / build-push-deploy (push) Successful in 1m3s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 31s
CI / test-python-voice (push) Successful in 30s
CI / test-bqas (push) Successful in 31s
2026-04-14 15:20:56 +02:00
Benjamin Admin 00d802f965 test(pitch-deck): banner text Draft → Draft V1 — deployment test
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 15:20:41 +02:00
Sharang Parnerkar ebb7575f2c test: retrigger with http:// webhook URL
Build pitch-deck / build-push-deploy (push) Successful in 15s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 33s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 30s
2026-04-14 09:32:36 +02:00
Sharang Parnerkar d0539d0f2f ci: use http:// for orca webhook (port 6880 serves plain HTTP)
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been cancelled
CI / nodejs-lint (push) Has been cancelled
CI / test-go-consent (push) Has been cancelled
CI / test-python-voice (push) Has been cancelled
CI / test-bqas (push) Has been cancelled
2026-04-14 09:32:08 +02:00
Sharang Parnerkar 8e92a93aa8 test: verify full CI pipeline with registry auth + orca webhook
Build pitch-deck / build-push-deploy (push) Failing after 14s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 34s
CI / test-python-voice (push) Successful in 34s
CI / test-bqas (push) Successful in 32s
2026-04-14 09:27:05 +02:00
Sharang Parnerkar f794347827 ci: add docker login step for registry.meghsakha.com
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 30s
CI / test-bqas (push) Has been cancelled
CI / test-python-voice (push) Has been cancelled
Requires Gitea Actions secrets: REGISTRY_USERNAME, REGISTRY_PASSWORD
2026-04-14 09:26:12 +02:00
Sharang Parnerkar 1af160eed0 test: trigger orca webhook via CI
Build pitch-deck / build-push-deploy (push) Failing after 10s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 33s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 32s
2026-04-14 09:22:10 +02:00
Sharang Parnerkar eb118ebf92 ci: re-add HMAC-SHA256 signing on orca webhook (ORCA_WEBHOOK_SECRET)
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 33s
CI / test-python-voice (push) Successful in 30s
CI / test-bqas (push) Successful in 31s
2026-04-14 08:31:29 +02:00
Sharang Parnerkar dbb476cc3b ci: drop HMAC signing (orca webhooks have no secret by default)
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 33s
CI / test-python-voice (push) Successful in 31s
CI / test-bqas (push) Successful in 32s
2026-04-14 08:27:22 +02:00
Sharang Parnerkar 9345efc3f0 ci(pipeline): trigger orca redeploy after image push, remove coolify
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 33s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 32s
build-pitch-deck workflow now posts an HMAC-signed push event to orca's
webhook endpoint after the image is built + pushed. This avoids the race
where orca would otherwise redeploy with the old :latest image before
CI finishes pushing the new one.

Removed the obsolete deploy-coolify.yml (wrong branch, wrong system) and
stripped the deploy-coolify job from ci.yaml.

Requires Gitea Actions secret: ORCA_WEBHOOK_SECRET_PITCH_DECK
2026-04-14 08:20:05 +02:00
Benjamin Admin c4e993e3f8 fix: Leere Controls (title/objective=None) filtern vor Store
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 44s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 30s
CI / Deploy (push) Failing after 4s
- Batch-Postprocessing: Controls mit title/objective = None/null/"" werden
  gefiltert und nicht gespeichert. Title wird aus Objective abgeleitet falls
  nur Title fehlt.
- _store_control: Pre-store Quality Guard lehnt leere Controls ab
- Verhindert "None"-Controls die durch LLM-Parsing-Fehler entstehen

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 06:59:47 +02:00
Benjamin Admin a58d1aa403 fix: KRITISCH — 12 Pipeline-Bugs gefixt, 36.000 verlorene Controls retten
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 36s
CI / test-python-voice (push) Successful in 37s
CI / test-bqas (push) Successful in 31s
CI / Deploy (push) Failing after 2s
Root Cause: _generate_control_id erzeugte ID-Kollisionen (String-Sort statt
numeric), ON CONFLICT DO NOTHING verwarf Controls stillschweigend, Chunks
wurden als "processed" markiert obwohl Store fehlschlug → permanent verloren.

Fixes:
1. _generate_control_id: Numeric MAX statt String-Sort, Collision Guard
   mit UUID-Suffix Fallback, Exception wird geloggt statt verschluckt
2. _store_control: ON CONFLICT DO UPDATE statt DO NOTHING → ID immer returned
3. Store-Logik: Chunk wird bei store_failed NICHT mehr als processed markiert
   → Retry beim naechsten Lauf moeglich
4. Counter: controls_generated nur bei erfolgreichem Store inkrementiert
   Neue Counter: controls_stored + controls_store_failed
5. Anthropic API: HTTP 429/500/502/503/504 werden jetzt retried (2 Versuche)
6. Monitoring: Progress-Log zeigt Store-Rate (%), ALARM bei <80%
7. Post-Job Validierung: Vergleicht Generated vs Stored vs DB-Realitaet
   WARNUNG wenn store_failed > 0, KRITISCH wenn Rate < 90%

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 00:39:12 +02:00
Benjamin Admin d7ed5ce8c5 fix(pitch-deck): add 8 missing slides to renderSlide switch
Build pitch-deck / build-and-push (push) Failing after 1m4s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 33s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 35s
CI / Deploy (push) Failing after 2s
ExecutiveSummary, RegulatoryLandscape, CapTable, Savings,
SDKDemo, Strategy, Finanzplan, Glossary

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 22:36:14 +02:00
Benjamin Admin 512088ab93 feat(pitch-deck): HTTPS via Nginx reverse proxy on port 3012
Build pitch-deck / build-and-push (push) Failing after 56s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 33s
CI / Deploy (push) Failing after 4s
- Add Nginx SSL server block for pitch-deck on port 3012
- Route through Nginx instead of direct container port
- Restore secure cookie flag (requires HTTPS)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 17:13:52 +02:00
Benjamin Admin 32b5e0223d fix(pitch-deck): use explicit PITCH_SECURE_COOKIE flag for cookie security
HTTP access on local network was blocked by secure cookie flag when
NODE_ENV=production. Now requires explicit opt-in via env var.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 17:11:36 +02:00
Benjamin Admin 9354cbf775 fix(pitch-deck): add PITCH_JWT_SECRET + PITCH_ADMIN_SECRET env vars
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 17:00:57 +02:00
Benjamin Admin 756d068b4f fix: skip_web_search Default auf True — 5x schnellere Pipeline
Anchor-Search (DuckDuckGo + RAG via SDK) verlangsamt Pipeline von
~50 Chunks/min auf ~10 Chunks/min. Anchors (OWASP/NIST-Referenzen)
koennen nachtraeglich in einem Batch-Job befuellt werden.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 12:26:01 +02:00
Benjamin Admin c02a7bd8a6 feat(pitch-deck): show version name + status in preview banner
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 12:21:59 +02:00
Benjamin Admin b6d3fad6ab Merge branch 'main' of ssh://gitea.meghsakha.com:22222/Benjamin_Boenisch/breakpilot-core into feature/payment-compliance-module 2026-04-13 11:49:25 +02:00
Sharang Parnerkar 27479ee553 docs(mcp-server): add README + gitignore .mcp.json
Build pitch-deck / build-and-push (push) Failing after 1m2s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 35s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 34s
CI / Deploy (push) Failing after 3s
Setup instructions for the pitch version MCP server.
.mcp.json contains the admin secret and is gitignored.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:36:54 +02:00
Sharang Parnerkar 82a5d62f44 feat(pitch-deck): MCP server for pitch version management via Claude Code
Build pitch-deck / build-and-push (push) Failing after 1m8s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 36s
CI / test-python-voice (push) Successful in 42s
CI / test-bqas (push) Successful in 40s
CI / Deploy (push) Failing after 3s
Stdio MCP server that wraps the pitch-deck admin API, exposing 11 tools:
list_versions, create_version, get_version, get_table_data,
update_table_data, commit_version, fork_version, diff_versions,
list_investors, assign_version, invite_investor.

Authenticates via PITCH_ADMIN_SECRET bearer token against the deployed
pitch-deck API. All existing auth, validation, and audit logging is
reused — the MCP server is a thin adapter.

Usage: add to ~/.claude/settings.json mcpServers, set PITCH_API_URL
and PITCH_ADMIN_SECRET env vars. See mcp-server/README.md (to be added).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:32:45 +02:00
104 changed files with 11148 additions and 2310 deletions
+227
View File
@@ -0,0 +1,227 @@
# AGENTS.go.md — Go Agent Rules
Applies to: `ai-compliance-sdk/` (Go/Gin service)
---
## NON-NEGOTIABLE: Pre-Push Checklist
**BEFORE every `git push`, run ALL of the following from the module root. A single failure blocks the push.**
```bash
# 1. Format (gofmt is non-negotiable — unformatted code fails CI)
gofmt -l . | grep -q . && echo "FORMATTING ERRORS — run: gofmt -w ." && exit 1 || true
# 2. Vet (catches suspicious code that compiles but is likely wrong)
go vet ./...
# 3. Lint (golangci-lint aggregates 50+ linters — the de-facto standard)
golangci-lint run --timeout=5m ./...
# 4. Tests with race detector
go test -race -count=1 ./...
# 5. Build verification (catches import errors, missing implementations)
go build ./...
```
**One-liner pre-push gate:**
```bash
gofmt -l . | grep -q . && exit 1; go vet ./... && golangci-lint run --timeout=5m && go test -race -count=1 ./... && go build ./...
```
### Why each check matters
| Check | Catches | Time |
|-------|---------|------|
| `gofmt` | Formatting violations (CI rejects unformatted code) | <1s |
| `go vet` | Printf format mismatches, unreachable code, shadowed vars | <5s |
| `golangci-lint` | 50+ static analysis checks (errcheck, staticcheck, etc.) | 10-30s |
| `go test -race` | Race conditions (invisible without this flag) | 10-60s |
| `go build` | Import errors, interface mismatches | <5s |
---
## golangci-lint Configuration
Config lives in `.golangci.yml` at the repo root. Minimum required linters:
```yaml
linters:
enable:
- errcheck # unchecked errors are bugs
- gosimple # code simplification
- govet # go vet findings
- ineffassign # useless assignments
- staticcheck # advanced static analysis (SA*, S*, QF*)
- unused # unused code
- gofmt # formatting
- goimports # import organization
- gocritic # opinionated style checks
- noctx # HTTP requests without context
- bodyclose # unclosed HTTP response bodies
- exhaustive # exhaustive switch on enums
- wrapcheck # errors from external packages must be wrapped
linters-settings:
errcheck:
check-blank: true # blank identifier for errors is a bug
govet:
enable-all: true
issues:
max-issues-per-linter: 0
max-same-issues: 0
```
**Never suppress with `//nolint:` without a comment explaining why it's safe.**
---
## Code Structure (Hexagonal Architecture)
```
ai-compliance-sdk/
├── cmd/
│ └── server/main.go # thin: parse flags, wire deps, call app.Run()
├── internal/
│ ├── app/ # dependency wiring
│ ├── domain/ # pure business logic, no framework deps
│ ├── ports/ # interfaces (repositories, external services)
│ ├── adapters/
│ │ ├── http/ # Gin handlers (≤30 LOC per handler)
│ │ ├── postgres/ # DB adapters implementing ports
│ │ └── external/ # third-party API clients
│ └── services/ # orchestration between domain + ports
└── pkg/ # exported, reusable packages
```
**Handler constraint — max 30 lines per handler:**
```go
func (h *RiskHandler) GetRisk(c *gin.Context) {
id, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
return
}
risk, err := h.service.Get(c.Request.Context(), id)
if err != nil {
h.handleError(c, err)
return
}
c.JSON(http.StatusOK, risk)
}
```
---
## Error Handling
```go
// REQUIRED: wrap errors with context
if err != nil {
return fmt.Errorf("get risk %s: %w", id, err)
}
// REQUIRED: define sentinel errors in domain package
var ErrNotFound = errors.New("not found")
var ErrUnauthorized = errors.New("unauthorized")
// REQUIRED: check errors — never use _ for error returns
result, err := service.Do(ctx, input)
if err != nil {
// handle it
}
```
**`errcheck` linter enforces this — zero tolerance for unchecked errors.**
---
## Testing Requirements
```
internal/
├── domain/
│ ├── risk.go
│ └── risk_test.go # unit: pure functions, no I/O
├── adapters/
│ ├── http/
│ │ ├── handler.go
│ │ └── handler_test.go # httptest-based, mock service
│ └── postgres/
│ ├── repo.go
│ └── repo_test.go # integration: testcontainers or real DB
```
**Test naming convention:**
```go
func TestRiskService_Get_ReturnsRisk(t *testing.T) {}
func TestRiskService_Get_NotFound_ReturnsError(t *testing.T) {}
func TestRiskService_Get_DBError_WrapsError(t *testing.T) {}
```
**Table-driven tests are mandatory for functions with multiple cases:**
```go
func TestValidateInput(t *testing.T) {
tests := []struct {
name string
input string
wantErr bool
}{
{"valid", "ok", false},
{"empty", "", true},
{"too long", strings.Repeat("x", 300), true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateInput(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("got err=%v, wantErr=%v", err, tt.wantErr)
}
})
}
}
```
```bash
# Pre-push: unit tests only (fast)
go test -race -count=1 -run "^TestUnit" ./...
# CI: all tests
go test -race -count=1 -coverprofile=coverage.out ./...
go tool cover -func=coverage.out | grep total
```
---
## Context Propagation
Every function that does I/O (DB, HTTP, file) **must** accept and pass `context.Context` as the first argument:
```go
// REQUIRED
func (r *RiskRepo) Get(ctx context.Context, id uuid.UUID) (*Risk, error) {
return r.db.QueryRowContext(ctx, query, id).Scan(...)
}
// FORBIDDEN — no context
func (r *RiskRepo) Get(id uuid.UUID) (*Risk, error) { ... }
```
`noctx` linter enforces HTTP client context. Manual review required for DB calls.
---
## Common Pitfalls That Break CI
| Pitfall | Prevention |
|---------|------------|
| Unformatted code | `gofmt -w .` before commit |
| Unchecked error return from `rows.Close()` / `resp.Body.Close()` | `errcheck` + `bodyclose` linters |
| Goroutine leak (goroutine started but never stopped) | `-race` test flag |
| Shadowed `err` variable in nested scope | `govet -shadow` |
| HTTP response body not closed | `bodyclose` linter |
| `interface{}` instead of `any` (Go 1.18+) | `gocritic` |
| Missing context on DB/HTTP calls | `noctx` linter |
| Returning concrete type from constructor instead of interface | breaks testability |
+157
View File
@@ -0,0 +1,157 @@
# AGENTS.python.md — Python Agent Rules
Applies to: `backend-compliance/`, `ai-compliance-sdk/` (Python path), `compliance-tts-service/`, `document-crawler/`, `dsms-gateway/` (Python services)
---
## NON-NEGOTIABLE: Pre-Push Checklist
**BEFORE every `git push`, run ALL of the following from the service directory. A single failure blocks the push.**
```bash
# 1. Fast lint (Ruff — catches syntax errors, unused imports, style violations)
ruff check .
# 2. Auto-fix safe issues, then re-check
ruff check --fix . && ruff check .
# 3. Type checking (mypy strict on new modules, standard on legacy)
mypy . --ignore-missing-imports --no-error-summary
# 4. Unit tests only (fast, no external deps)
pytest tests/unit/ -x -q --no-header
# 5. Verify the service starts (catches import errors, missing env vars with defaults)
python -c "import app" 2>/dev/null || python -c "import main" 2>/dev/null || true
```
**One-liner pre-push gate (run from service root):**
```bash
ruff check . && mypy . --ignore-missing-imports --no-error-summary && pytest tests/ -x -q --no-header
```
### Why each check matters
| Check | Catches | Time |
|-------|---------|------|
| `ruff check` | Syntax errors, unused imports, undefined names | <2s |
| `mypy` | Type mismatches, wrong argument types | 5-15s |
| `pytest -x` | Logic errors, regressions | 10-60s |
| import check | Missing packages, circular imports | <1s |
---
## Code Style (Ruff)
Config lives in `pyproject.toml`. Do **not** add per-file `# noqa` suppressions without a comment explaining why.
```toml
[tool.ruff]
line-length = 100
target-version = "py311"
[tool.ruff.lint]
select = ["E", "F", "W", "I", "N", "UP", "B", "C4", "SIM", "TCH"]
ignore = ["E501"] # line length handled by formatter
[tool.ruff.lint.per-file-ignores]
"tests/*" = ["S101"] # assert is fine in tests
```
**Blocked patterns:**
- `from module import *` — always name imports explicitly
- Bare `except:` — use `except Exception as e:` at minimum
- `print()` in production code — use `logger`
- Mutable default arguments: `def f(x=[])``def f(x=None)`
---
## Type Annotations
All new functions **must** have complete type annotations. Use `from __future__ import annotations` for forward references.
```python
# Required
async def get_tenant(tenant_id: str, db: AsyncSession) -> TenantModel | None:
...
# Required for complex types
from typing import Sequence
def list_risks(filters: dict[str, str]) -> Sequence[RiskModel]:
...
```
**Mypy rules:**
- `--disallow-untyped-defs` on new files
- `--strict` on new modules (not legacy)
- Never use `type: ignore` without a comment
---
## FastAPI-Specific Rules
```python
# Handlers stay thin — delegate to service layer
@router.get("/risks/{risk_id}", response_model=RiskResponse)
async def get_risk(risk_id: UUID, service: RiskService = Depends(get_risk_service)):
return await service.get(risk_id) # ≤5 lines per handler
# Always use response_model — never return raw dicts from endpoints
# Always validate input with Pydantic — no manual dict parsing
# Use HTTPException with specific status codes, never bare 500
```
---
## Testing Requirements
```
tests/
├── unit/ # Pure logic tests, no DB/HTTP (run on every push)
├── integration/ # Requires running services (run in CI only)
└── contracts/ # OpenAPI snapshot tests (run on API changes)
```
**Unit test requirements:**
- Every new function → at least one happy-path test
- Every bug fix → regression test that would have caught it
- Mock all I/O: DB calls, HTTP calls, filesystem reads
```bash
# Run unit tests only (fast, for pre-push)
pytest tests/unit/ -x -q
# Run with coverage (for CI)
pytest tests/ --cov=. --cov-report=term-missing --cov-fail-under=70
```
---
## Dependency Management
```bash
# Check new package license before adding
pip show <package> | grep -E "License|Home-page"
# After adding to requirements.txt — verify no GPL/AGPL
pip-licenses --fail-on="GPL;AGPL" 2>/dev/null || echo "Check licenses manually"
```
**Never add:**
- GPL/AGPL licensed packages
- Packages with known CVEs (`pip audit`)
- Packages that only exist for dev (`pytest`, `ruff`) to production requirements
---
## Common Pitfalls That Break CI
| Pitfall | Prevention |
|---------|------------|
| `const x = ...` inside dict literal (wrong language!) | Run ruff before push |
| Pydantic v1 syntax in v2 project | Use `model_config`, not `class Config` |
| Sync function called inside async without `run_in_executor` | mypy + async linter |
| Missing `await` on coroutine | mypy catches this |
| `datetime.utcnow()` (deprecated) | Use `datetime.now(timezone.utc)` |
| Bare `except:` swallowing errors silently | ruff B001/E722 catches this |
| Unused imports left in committed code | ruff F401 catches this |
+186
View File
@@ -0,0 +1,186 @@
# AGENTS.typescript.md — TypeScript/Next.js Agent Rules
Applies to: `pitch-deck/`, `admin-v2/` (Next.js apps in this repo)
---
## NON-NEGOTIABLE: Pre-Push Checklist
**BEFORE every `git push`, run ALL of the following from the Next.js app directory. A single failure blocks the push.**
```bash
# 1. Type check (catches the class of bug that broke ChatFAB.tsx — const inside object)
npx tsc --noEmit
# 2. Lint (ESLint with TypeScript-aware rules)
npm run lint
# 3. Production build (THE most important check — passes lint/types but still fails build)
npm run build
```
**One-liner pre-push gate:**
```bash
npx tsc --noEmit && npm run lint && npm run build
```
> **Why `npm run build` is mandatory:** Next.js performs additional checks during build (server component boundaries, missing env vars referenced in code, RSC/client component violations) that `tsc` and ESLint alone do not catch. The ChatFAB syntax error (`const` inside object literal) is exactly the kind of error caught only by build.
### Why each check matters
| Check | Catches | Time |
|-------|---------|------|
| `tsc --noEmit` | Type errors, wrong prop types, missing members | 5-20s |
| `eslint` | React hooks rules, import order, unused vars | 5-15s |
| `next build` | Server/client boundary violations, missing deps, syntax errors in JSX, env var issues | 30-120s |
---
## TypeScript Configuration
`tsconfig.json` must have strict mode enabled:
```json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
}
}
```
**Never use `// @ts-ignore` or `// @ts-expect-error` without a comment explaining why it's unavoidable.**
---
## ESLint Configuration
```json
{
"extends": [
"next/core-web-vitals",
"plugin:@typescript-eslint/recommended-type-checked"
],
"rules": {
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/await-thenable": "error",
"react-hooks/exhaustive-deps": "error",
"no-console": "warn"
}
}
```
**`@typescript-eslint/no-floating-promises`** — catches `await`-less async calls that silently swallow errors.
**`react-hooks/exhaustive-deps`** — catches missing deps in `useEffect`/`useCallback` (source of stale closure bugs).
---
## Next.js 15 Rules (App Router)
### Server vs Client boundary
```typescript
// Server Component (default) — no 'use client' needed
// Can: fetch data, access DB, read env vars, import server-only packages
async function Page() {
const data = await fetchData() // direct async/await
return <ClientComponent data={data} />
}
// Client Component — must have 'use client' at top
'use client'
// Can: use hooks, handle events, access browser APIs
// Cannot: import server-only packages (nodemailer, fs, db pool)
```
**Common violation:** Importing `lib/email.ts` (which imports nodemailer) from a client component → use `lib/email-templates.ts` instead.
### Route Handler typing
```typescript
// Always type request and use NextResponse
export async function GET(request: Request): Promise<NextResponse> {
const { searchParams } = new URL(request.url)
return NextResponse.json({ data })
}
```
### Environment variables
```typescript
// Server-only env vars: access directly
const secret = process.env.PITCH_ADMIN_SECRET // fine in server components
// Client env vars: must be prefixed NEXT_PUBLIC_
const url = process.env.NEXT_PUBLIC_API_URL // accessible in browser
// Never access server-only env vars in 'use client' components
```
---
## Component Architecture
```
app/
├── (route-group)/
│ ├── page.tsx # Server Component — data fetching
│ └── _components/ # Colocated components for this route
│ ├── ClientThing.tsx # 'use client' when needed
│ └── ServerThing.tsx # Server by default
components/
│ └── ui/ # Shared presentational components
lib/
│ ├── server-only-module.ts # import 'server-only' at top
│ └── shared-module.ts # safe for both server and client
```
**Rules:**
- Push `'use client'` boundary as deep as possible (toward leaves)
- Never import server-only modules from client components
- Colocate `_components/` and `_hooks/` per route when they're route-specific
---
## Testing Requirements
```bash
# Type check (fastest, run first)
npx tsc --noEmit
# Unit tests (Vitest)
npx vitest run
# E2E tests (Playwright — CI only, requires running server)
npx playwright test
```
**Test every:**
- Custom hook (`usePresenterMode`, `useSlideNavigation`)
- Utility function (`lib/auth.ts` helpers, `lib/email-templates.ts`)
- API route handler (mock DB, assert response shape)
---
## Common Pitfalls That Break CI
| Pitfall | Prevention |
|---------|------------|
| `const x = ...` inside object literal | `tsc --noEmit` + `npm run build` |
| Server-only import in client component | `import 'server-only'` guard + ESLint |
| Missing `await` on async function call | `@typescript-eslint/no-floating-promises` |
| `useEffect` with missing dependency | `react-hooks/exhaustive-deps` error |
| `any` type hiding type errors | `@typescript-eslint/no-explicit-any` error |
| Unused variable left after refactor | `noUnusedLocals` in tsconfig |
| `process.env.SECRET` in client component | Next.js build error |
| Forgetting `export default` on page component | Next.js build error |
| Calling server action from server component | must use route handler instead |
| `jose` full import in Edge Runtime | Use specific subpath: `jose/jwt/verify` |
+46 -15
View File
@@ -2,29 +2,29 @@
## Entwicklungsumgebung (WICHTIG - IMMER ZUERST LESEN)
### Zwei-Rechner-Setup + Coolify
### Zwei-Rechner-Setup + Orca
| Geraet | Rolle | Aufgaben |
|--------|-------|----------|
| **MacBook** | Entwicklung | Claude Terminal, Code-Entwicklung, Browser (Frontend-Tests) |
| **Mac Mini** | Lokaler Server | Docker fuer lokale Dev/Tests (NICHT fuer Production!) |
| **Coolify** | Production | Automatisches Build + Deploy bei Push auf gitea |
| **Orca** | Production | Automatisches Build + Deploy bei Push auf gitea |
**WICHTIG:** Code wird direkt auf dem MacBook in diesem Repo bearbeitet. Production-Deployment laeuft automatisch ueber Coolify.
**WICHTIG:** Code wird direkt auf dem MacBook in diesem Repo bearbeitet. Production-Deployment laeuft automatisch ueber Orca.
### Entwicklungsworkflow (CI/CD — Coolify)
### Entwicklungsworkflow (CI/CD — Orca)
```bash
# 1. Code auf MacBook bearbeiten (dieses Verzeichnis)
# 2. Committen und zu BEIDEN Remotes pushen:
git push origin main && git push gitea main
git push origin main
# 3. FERTIG! Push auf gitea triggert automatisch:
# - Gitea Actions: Tests
# - Coolify: Build → Deploy
# - Orca: Build → Deploy
```
**NIEMALS** manuell in Coolify auf "Redeploy" klicken — Gitea Actions triggert Coolify automatisch.
**NIEMALS** manuell in Orca auf "Redeploy" klicken — Gitea Actions triggert Orca automatisch.
**IMMER auf `main` pushen** — sowohl origin als auch gitea.
### Post-Push Deploy-Monitoring (PFLICHT nach jedem Push auf gitea)
@@ -39,7 +39,7 @@ git push origin main && git push gitea main
```
3. Sobald ALLE Endpoints healthy sind, dem User im Chat melden:
**"Deploy abgeschlossen! Du kannst jetzt testen."**
4. Falls nach 5 Minuten noch nicht healthy → Fehlermeldung mit Hinweis auf Coolify-Logs.
4. Falls nach 5 Minuten noch nicht healthy → Fehlermeldung mit Hinweis auf Orca-Logs.
### Lokale Entwicklung (Mac Mini — optional, nur Dev/Tests)
@@ -80,8 +80,8 @@ networks:
| Repo | Deployment | Trigger |
|------|-----------|---------|
| **breakpilot-core** | Coolify (automatisch) | Push auf gitea main |
| **breakpilot-compliance** | Coolify (automatisch) | Push auf gitea main |
| **breakpilot-core** | Orca (automatisch) | Push auf gitea main |
| **breakpilot-compliance** | Orca (automatisch) | Push auf gitea main |
| **breakpilot-lehrer** | Mac Mini (lokal) | Manuell docker compose |
---
@@ -252,8 +252,8 @@ ssh macmini "/usr/local/bin/docker logs -f bp-core-control-pipeline"
### Deployment (CI/CD — Standardweg)
```bash
# Committen und pushen → Coolify deployt automatisch:
git push origin main && git push gitea main
# Committen und pushen → Orca deployt automatisch:
git push origin main
```
### Lokale Docker-Befehle (Mac Mini — nur Dev/Tests)
@@ -278,15 +278,46 @@ ssh macmini "/usr/local/bin/docker ps --filter name=bp-core"
```bash
# Zu BEIDEN Remotes pushen (PFLICHT!):
git push origin main && git push gitea main
git push origin main
# Remotes:
# origin: lokale Gitea (macmini:3003)
# gitea: gitea.meghsakha.com
```
---
## Pre-Push Checks (PFLICHT — VOR JEDEM PUSH)
> Full detail: `.claude/rules/pre-push-checks.md` | Stack rules: `AGENTS.python.md`, `AGENTS.go.md`, `AGENTS.typescript.md`
**NIEMALS pushen ohne diese Checks. CI-Failures blockieren das gesamte Deploy.**
### Python (backend-core, rag-service, embedding-service, control-pipeline)
```bash
cd <service-dir>
ruff check . && mypy . --ignore-missing-imports --no-error-summary && pytest tests/ -x -q --no-header
```
### Go (consent-service, billing-service)
```bash
cd <service-dir>
gofmt -l . | grep -q . && exit 1; go vet ./... && golangci-lint run --timeout=5m && go test -race ./... && go build ./...
```
### TypeScript/Next.js (pitch-deck, admin-v2)
```bash
cd pitch-deck # or admin-v2
npx tsc --noEmit && npm run lint && npm run build
```
> `npm run build` ist PFLICHT — `tsc` allein reicht nicht. Syntax-Fehler wie `const` inside object literal werden nur vom Build gefangen.
---
## Kernprinzipien
### 1. Open Source Policy
+74
View File
@@ -0,0 +1,74 @@
# Pre-Push Checks (MANDATORY)
## Rule
**NEVER push to any remote without first running and confirming ALL checks pass for every changed language stack.**
This rule exists because CI failures break the deploy pipeline for everyone and waste ~5 minutes per failed build. A 60-second local check prevents that.
---
## Quick Reference by Stack
### Python (backend-compliance, ai-compliance-sdk, compliance-tts-service)
```bash
cd <service-dir>
ruff check . && mypy . --ignore-missing-imports --no-error-summary && pytest tests/ -x -q --no-header
```
Blocks on: syntax errors, type errors, failing tests.
### Go (ai-compliance-sdk Go path)
```bash
cd <service-dir>
gofmt -l . | grep -q . && exit 1; go vet ./... && golangci-lint run --timeout=5m && go test -race ./... && go build ./...
```
Blocks on: formatting, vet findings, lint violations, test failures, build errors.
### TypeScript/Next.js (admin-compliance, developer-portal)
```bash
cd <nextjs-app-dir>
npx tsc --noEmit && npm run lint && npm run build
```
Blocks on: type errors, lint violations, **build failures**.
> `npm run build` is mandatory — `tsc` passes but `next build` fails more often than you'd expect (server/client boundary violations, env var issues, JSX syntax errors).
---
## What Claude Must Do Before Every Push
1. Identify which services/apps were changed in this task
2. Run the appropriate gate command(s) from the table above
3. If any check fails: fix it, re-run, confirm green
4. Only then run `git push origin main`
**No exceptions.** A push that skips pre-push checks and breaks CI is worse than a delayed push.
---
## CI vs Local Checks
| Stage | Where | What |
|-------|-------|------|
| Pre-push (local) | Claude runs | Lint + type check + unit tests + build |
| CI (Gitea Actions) | Automatic on push | Same + integration tests + contract tests |
| Deploy (Orca) | Automatic after CI | Docker build + health check |
Local checks catch 90% of CI failures in seconds. CI is the safety net, not the first line of defense.
---
## Failures That Were Caused by Skipping Pre-Push Checks
- `ChatFAB.tsx`: `const textLang` inside fetch object literal — caught by `tsc --noEmit` and `npm run build`
- `nodemailer` webpack error: server-only import in client component — caught by `npm run build`
- `jose` Edge Runtime error: full package import — caught by `npm run build`
- `main.py` `<en>` tags spoken: missing `import re` — caught by `python -c "import main"`
These all caused a broken deploy. Each would have been caught in <60 seconds locally.
+33 -4
View File
@@ -1,5 +1,8 @@
# Build + push pitch-deck Docker image to registry.meghsakha.com
# on every push to main that touches pitch-deck/ files.
# and trigger orca redeploy on every push to main that touches pitch-deck/.
#
# Requires Gitea Actions secret: ORCA_WEBHOOK_SECRET
# (must match the `secret` field in ~/.orca/webhooks.json on the orca master)
name: Build pitch-deck
@@ -10,21 +13,29 @@ on:
- 'pitch-deck/**'
jobs:
build-and-push:
build-push-deploy:
runs-on: docker
container:
image: docker:27-cli
steps:
- name: Checkout
run: |
apk add --no-cache git
apk add --no-cache git openssl curl
git clone --depth 1 --branch ${GITHUB_REF_NAME} ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git .
- name: Login to registry
env:
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
run: |
echo "$REGISTRY_PASSWORD" | docker login registry.meghsakha.com -u "$REGISTRY_USERNAME" --password-stdin
- name: Build image
run: |
cd pitch-deck
SHORT_SHA=$(git rev-parse --short HEAD)
docker build \
--build-arg GIT_SHA=${SHORT_SHA} \
-t registry.meghsakha.com/breakpilot/pitch-deck:latest \
-t registry.meghsakha.com/breakpilot/pitch-deck:${SHORT_SHA} \
.
@@ -34,4 +45,22 @@ jobs:
SHORT_SHA=$(git rev-parse --short HEAD)
docker push registry.meghsakha.com/breakpilot/pitch-deck:latest
docker push registry.meghsakha.com/breakpilot/pitch-deck:${SHORT_SHA}
echo "Pushed registry.meghsakha.com/breakpilot/pitch-deck:latest + :${SHORT_SHA}"
echo "Pushed :latest + :${SHORT_SHA}"
- name: Trigger orca redeploy
env:
ORCA_WEBHOOK_SECRET: ${{ secrets.ORCA_WEBHOOK_SECRET }}
ORCA_WEBHOOK_URL: http://46.225.100.82:6880/api/v1/webhooks/github
run: |
SHA=$(git rev-parse HEAD)
PAYLOAD="{\"ref\":\"refs/heads/main\",\"repository\":{\"full_name\":\"${GITHUB_REPOSITORY}\"},\"head_commit\":{\"id\":\"$SHA\",\"message\":\"ci: pitch-deck image build\"}}"
SIG=$(printf '%s' "$PAYLOAD" | openssl dgst -sha256 -hmac "$ORCA_WEBHOOK_SECRET" -r | awk '{print $1}')
curl -sSf -k \
-X POST \
-H "Content-Type: application/json" \
-H "X-GitHub-Event: push" \
-H "X-Hub-Signature-256: sha256=$SIG" \
-d "$PAYLOAD" \
"$ORCA_WEBHOOK_URL" \
|| { echo "Orca redeploy failed"; exit 1; }
echo "Orca redeploy triggered"
+2 -16
View File
@@ -140,20 +140,6 @@ jobs:
python -m pytest tests/bqas/ -v --tb=short || true
# ========================================
# Deploy via Coolify (nur main, kein PR)
# Deploys now handled by per-service workflows (e.g. build-pitch-deck.yml)
# which trigger orca webhooks directly after building + pushing the image.
# ========================================
deploy-coolify:
name: Deploy
runs-on: docker
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs:
- test-go-consent
container:
image: alpine:latest
steps:
- name: Trigger Coolify deploy
run: |
apk add --no-cache curl
curl -sf "${{ secrets.COOLIFY_WEBHOOK }}" \
-H "Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}"
-27
View File
@@ -1,27 +0,0 @@
name: Deploy to Coolify
on:
push:
branches:
- coolify
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy via Coolify API
run: |
echo "Deploying breakpilot-core to Coolify..."
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-X POST \
-H "Authorization: Bearer ${{ secrets.COOLIFY_API_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{"uuid": "${{ secrets.COOLIFY_RESOURCE_UUID }}", "force_rebuild": true}' \
"${{ secrets.COOLIFY_BASE_URL }}/api/v1/deploy")
echo "HTTP Status: $HTTP_STATUS"
if [ "$HTTP_STATUS" -ne 200 ] && [ "$HTTP_STATUS" -ne 201 ]; then
echo "Deployment failed with status $HTTP_STATUS"
exit 1
fi
echo "Deployment triggered successfully!"
+1
View File
@@ -7,6 +7,7 @@
secrets/
*.pem
*.key
.mcp.json
# Node
node_modules/
@@ -37,6 +37,8 @@ from services.control_generator import (
)
from services.citation_backfill import CitationBackfill, BackfillResult
from services.rag_client import get_rag_client
from services.anchor_finder import AnchorFinder, OpenAnchor
from services.control_generator import GeneratedControl as _GeneratedControl
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/v1/canonical", tags=["control-generator"])
@@ -52,7 +54,7 @@ class GenerateRequest(BaseModel):
max_controls: int = 50
max_chunks: int = 1000 # Default: process max 1000 chunks per job (respects document boundaries)
batch_size: int = 5
skip_web_search: bool = False
skip_web_search: bool = True # Default True — Anchors nachtraeglich batchen
dry_run: bool = False
regulation_filter: Optional[List[str]] = None # Only process these regulation_code prefixes
regulation_exclude: Optional[List[str]] = None # Skip these regulation_code prefixes
@@ -1100,3 +1102,464 @@ async def get_source_type_backfill_status(backfill_id: str):
if not status:
raise HTTPException(status_code=404, detail="Source-type backfill job not found")
return status
# =============================================================================
# REPAIR BACKFILL — Fix controls with missing title/objective/requirements
# =============================================================================
class RepairBackfillRequest(BaseModel):
dry_run: bool = True
limit: int = 0 # 0 = all
batch_size: int = 10
_repair_backfill_status: dict = {}
async def _run_repair_backfill(req: RepairBackfillRequest, backfill_id: str):
"""Repair controls with missing title, objective, or requirements using Anthropic API."""
import os
import httpx
ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY", "")
ANTHROPIC_MODEL = os.getenv("CONTROL_GEN_ANTHROPIC_MODEL", "claude-sonnet-4-6")
if not ANTHROPIC_API_KEY:
_repair_backfill_status[backfill_id] = {
"status": "failed", "error": "ANTHROPIC_API_KEY not set"
}
return
db = SessionLocal()
try:
# Find controls needing repair: missing title OR missing objective OR missing requirements
limit_clause = f"LIMIT {req.limit}" if req.limit > 0 else ""
rows = db.execute(text(f"""
SELECT id, control_id, title, objective, requirements::text as requirements,
source_original_text, tags::text as tags, category
FROM compliance.canonical_controls
WHERE release_state = 'draft'
AND (
(title IS NULL OR title = 'None' OR title = '')
OR (objective IS NULL OR objective = 'None' OR objective = '')
OR (requirements IS NULL OR requirements::text = '[]' OR requirements::text = 'null')
)
ORDER BY control_id
{limit_clause}
""")).fetchall()
total = len(rows)
repaired = 0
skipped = 0
errors = []
_repair_backfill_status[backfill_id] = {
"status": "running", "total": total, "repaired": 0, "skipped": 0,
"dry_run": req.dry_run, "errors": [],
}
for i in range(0, total, req.batch_size):
batch = rows[i:i + req.batch_size]
entries = []
for idx, row in enumerate(batch):
# Collect all available context
available = []
if row.title and row.title != "None":
available.append(f"Titel: {row.title}")
if row.objective and row.objective != "None":
available.append(f"Objective: {row.objective[:500]}")
if row.requirements and row.requirements not in ("[]", "null", "None"):
available.append(f"Requirements: {row.requirements[:500]}")
if row.source_original_text and len(row.source_original_text) > 20:
available.append(f"Quelltext: {row.source_original_text[:800]}")
if row.category:
available.append(f"Kategorie: {row.category}")
missing = []
if not row.title or row.title == "None":
missing.append("title")
if not row.objective or row.objective == "None":
missing.append("objective")
if not row.requirements or row.requirements in ("[]", "null", "None"):
missing.append("requirements")
entries.append(
f"--- CONTROL {idx + 1}: {row.control_id} ---\n"
f"Fehlend: {', '.join(missing)}\n"
f"{'chr(10)'.join(available)}\n"
)
prompt = f"""Repariere die folgenden {len(batch)} Compliance-Controls. Fuer jedes Control fehlen bestimmte Felder.
Regeln:
- title: Kurzer, praegnanter deutscher Titel (max 80 Zeichen). Erster Buchstabe gross.
- objective: 1-2 Saetze die das Ziel des Controls beschreiben.
- requirements: JSON-Array mit 2-5 konkreten Anforderungen als Strings.
- Nur die fehlenden Felder generieren. Bestehende Felder NICHT aendern.
- Wenn nicht genug Kontext vorhanden ist, schreibe "SKIP" als Wert.
Antworte mit einem JSON-Array. Jedes Objekt hat:
- control_index: 1-basierter Index
- title: (nur wenn fehlend, sonst null)
- objective: (nur wenn fehlend, sonst null)
- requirements: (nur wenn fehlend, sonst null — als JSON-Array von Strings)
{chr(10).join(entries)}"""
try:
headers = {
"x-api-key": ANTHROPIC_API_KEY,
"anthropic-version": "2023-06-01",
"content-type": "application/json",
}
payload = {
"model": ANTHROPIC_MODEL,
"max_tokens": 4096,
"system": "Du bist ein Compliance-Experte. Repariere unvollstaendige Controls. Antworte NUR mit validem JSON.",
"messages": [{"role": "user", "content": prompt}],
}
async with httpx.AsyncClient(timeout=120.0) as client:
resp = await client.post(
"https://api.anthropic.com/v1/messages",
headers=headers,
json=payload,
)
if resp.status_code != 200:
errors.append(f"Batch {i}: API {resp.status_code}")
continue
content = resp.json().get("content", [{}])[0].get("text", "")
parsed = _parse_llm_json(content)
if not parsed:
errors.append(f"Batch {i}: JSON parse failed")
continue
# Handle single object response
items = parsed if isinstance(parsed, list) else [parsed]
for item in items:
idx = item.get("control_index", 0) - 1
if idx < 0 or idx >= len(batch):
continue
row = batch[idx]
updates = []
params = {"cid": str(row.id)}
new_title = item.get("title")
if new_title and new_title != "SKIP" and (not row.title or row.title == "None"):
updates.append("title = :title")
params["title"] = new_title
new_obj = item.get("objective")
if new_obj and new_obj != "SKIP" and (not row.objective or row.objective == "None"):
updates.append("objective = :objective")
params["objective"] = new_obj
new_req = item.get("requirements")
if new_req and new_req != "SKIP" and (not row.requirements or row.requirements in ("[]", "null", "None")):
if isinstance(new_req, list):
updates.append("requirements = CAST(:requirements AS jsonb)")
params["requirements"] = json.dumps(new_req)
if not updates:
skipped += 1
continue
if not req.dry_run:
updates.append("updated_at = NOW()")
db.execute(text(f"""
UPDATE compliance.canonical_controls
SET {', '.join(updates)}
WHERE id = CAST(:cid AS uuid)
"""), params)
repaired += 1
if not req.dry_run:
db.commit()
except Exception as e:
errors.append(f"Batch {i}: {str(e)[:200]}")
logger.warning("Repair backfill batch %d error: %s", i, e)
db.rollback()
_repair_backfill_status[backfill_id] = {
"status": "running", "total": total, "repaired": repaired, "skipped": skipped,
"progress": f"{min(i + req.batch_size, total)}/{total}",
"dry_run": req.dry_run, "errors": errors[-10:],
}
_repair_backfill_status[backfill_id] = {
"status": "completed", "total": total, "repaired": repaired, "skipped": skipped,
"dry_run": req.dry_run, "errors": errors[-50:],
}
logger.info("Repair backfill %s completed: %d/%d repaired, %d skipped",
backfill_id, repaired, total, skipped)
except Exception as e:
logger.error("Repair backfill %s failed: %s", backfill_id, e)
_repair_backfill_status[backfill_id] = {"status": "failed", "error": str(e)}
finally:
db.close()
@router.post("/generate/backfill-repair")
async def start_repair_backfill(req: RepairBackfillRequest):
"""Repair controls with missing title, objective, or requirements using Anthropic API.
Finds draft controls where title/objective/requirements are missing or empty,
and generates the missing fields from available context (source text, other fields).
Default is dry_run=True (preview only, no DB changes).
"""
import uuid
backfill_id = str(uuid.uuid4())[:8]
_repair_backfill_status[backfill_id] = {"status": "starting"}
asyncio.create_task(_run_repair_backfill(req, backfill_id))
return {
"status": "running",
"backfill_id": backfill_id,
"message": f"Repair backfill started. Poll /generate/repair-backfill-status/{backfill_id}",
}
@router.get("/generate/repair-backfill-status/{backfill_id}")
async def get_repair_backfill_status(backfill_id: str):
"""Get status of a repair backfill job."""
status = _repair_backfill_status.get(backfill_id)
if not status:
raise HTTPException(status_code=404, detail="Repair backfill job not found")
return status
# =============================================================================
# BATCH DEDUP
# =============================================================================
class BatchDedupRequest(BaseModel):
dry_run: bool = True
hint_filter: Optional[str] = None # Only process groups matching this hint prefix
_batch_dedup_status: dict = {}
async def _run_batch_dedup(req: BatchDedupRequest, dedup_id: str):
"""Run batch dedup in background."""
from services.batch_dedup_runner import BatchDedupRunner
db = SessionLocal()
try:
runner = BatchDedupRunner(db)
_batch_dedup_status[dedup_id] = {"status": "running", "phase": "starting"}
stats = await runner.run(dry_run=req.dry_run, hint_filter=req.hint_filter)
_batch_dedup_status[dedup_id] = {
"status": "completed",
"dry_run": req.dry_run,
**stats,
}
logger.info("BatchDedup %s completed: %s", dedup_id, stats)
except Exception as e:
logger.error("BatchDedup %s failed: %s", dedup_id, e)
_batch_dedup_status[dedup_id] = {"status": "failed", "error": str(e)}
finally:
db.close()
@router.post("/generate/batch-dedup")
async def start_batch_dedup(req: BatchDedupRequest):
"""Run batch deduplication on Pass 0b atomic controls.
Phase 1: Intra-group dedup (same merge_group_hint → pick best, link rest)
Phase 2: Cross-group dedup (embed masters, search Qdrant for similar)
Default is dry_run=True (preview only, no DB changes).
"""
import uuid
dedup_id = str(uuid.uuid4())[:8]
_batch_dedup_status[dedup_id] = {"status": "starting"}
asyncio.create_task(_run_batch_dedup(req, dedup_id))
return {
"status": "running",
"dedup_id": dedup_id,
"message": f"BatchDedup started. Poll /generate/batch-dedup-status/{dedup_id}",
}
@router.get("/generate/batch-dedup-status/{dedup_id}")
async def get_batch_dedup_status(dedup_id: str):
"""Get status of a batch dedup job."""
status = _batch_dedup_status.get(dedup_id)
if not status:
raise HTTPException(status_code=404, detail="BatchDedup job not found")
return status
# =============================================================================
# ANCHOR BACKFILL
# =============================================================================
class AnchorBackfillRequest(BaseModel):
dry_run: bool = True
limit: int = 0 # 0 = all controls without anchors
batch_size: int = 50
skip_web: bool = True # Stage A only (RAG), no DuckDuckGo
include_needs_review: bool = True # Also backfill needs_review controls
_anchor_backfill_status: dict = {}
async def _run_anchor_backfill(req: AnchorBackfillRequest, backfill_id: str):
"""Backfill open_anchors for controls that were generated with skip_web_search=true."""
from dataclasses import asdict
db = SessionLocal()
try:
finder = AnchorFinder()
# Find controls without anchors
states = "('draft', 'needs_review')" if req.include_needs_review else "('draft',)"
limit_clause = f"LIMIT {req.limit}" if req.limit > 0 else ""
rows = db.execute(text(f"""
SELECT id, control_id, title, tags
FROM compliance.canonical_controls
WHERE release_state IN {states}
AND (open_anchors IS NULL OR open_anchors::text = '[]'
OR open_anchors::text = 'null' OR open_anchors::text = '')
ORDER BY control_id
{limit_clause}
""")).fetchall()
total = len(rows)
updated = 0
with_anchors = 0
empty_anchors = 0
errors = []
_anchor_backfill_status[backfill_id] = {
"status": "running", "total": total, "updated": 0,
"with_anchors": 0, "empty_anchors": 0, "dry_run": req.dry_run,
}
for i in range(0, total, req.batch_size):
batch = rows[i:i + req.batch_size]
for row in batch:
try:
# Parse tags from DB (may be JSON string or list)
tags = row.tags
if isinstance(tags, str):
try:
tags = json.loads(tags)
except (json.JSONDecodeError, ValueError):
tags = []
if not isinstance(tags, list):
tags = []
# Build minimal GeneratedControl for AnchorFinder
control = _GeneratedControl(
title=row.title or "",
tags=tags,
)
# Find anchors (Stage A: RAG only)
anchors = await finder.find_anchors(
control, skip_web=req.skip_web, min_anchors=2
)
anchor_dicts = [asdict(a) for a in anchors]
if not req.dry_run:
db.execute(text("""
UPDATE compliance.canonical_controls
SET open_anchors = CAST(:anchors AS jsonb),
updated_at = NOW()
WHERE id = CAST(:cid AS uuid)
"""), {"anchors": json.dumps(anchor_dicts), "cid": str(row.id)})
updated += 1
if anchor_dicts:
with_anchors += 1
else:
empty_anchors += 1
except Exception as e:
errors.append(f"{row.control_id}: {str(e)[:200]}")
logger.warning("Anchor backfill error for %s: %s", row.control_id, e)
if not req.dry_run:
db.commit()
_anchor_backfill_status[backfill_id] = {
"status": "running", "total": total, "updated": updated,
"with_anchors": with_anchors, "empty_anchors": empty_anchors,
"progress": f"{min(i + req.batch_size, total)}/{total}",
"dry_run": req.dry_run, "errors": errors[-10:],
}
# Promote needs_review controls that gained anchors to draft
promoted = 0
if not req.dry_run and req.include_needs_review:
result = db.execute(text("""
UPDATE compliance.canonical_controls
SET release_state = 'draft', updated_at = NOW()
WHERE release_state = 'needs_review'
AND open_anchors IS NOT NULL
AND open_anchors::text != '[]'
AND open_anchors::text != 'null'
AND open_anchors::text != ''
RETURNING id
"""))
promoted = result.rowcount
db.commit()
_anchor_backfill_status[backfill_id] = {
"status": "completed", "total": total, "updated": updated,
"with_anchors": with_anchors, "empty_anchors": empty_anchors,
"promoted_to_draft": promoted,
"dry_run": req.dry_run, "errors": errors[-50:],
}
logger.info("Anchor backfill %s completed: %d/%d updated (%d with anchors, %d empty, %d promoted)",
backfill_id, updated, total, with_anchors, empty_anchors, promoted)
except Exception as e:
logger.error("Anchor backfill %s failed: %s", backfill_id, e)
_anchor_backfill_status[backfill_id] = {"status": "failed", "error": str(e)}
finally:
db.close()
@router.post("/generate/backfill-anchors")
async def start_anchor_backfill(req: AnchorBackfillRequest):
"""Backfill open_anchors (OWASP/NIST/ENISA references) for controls without anchors.
Uses RAG-internal search (Stage A) to find open-source framework references.
Controls generated with skip_web_search=true have empty open_anchors.
Default is dry_run=True (preview only, no DB changes).
"""
import uuid
backfill_id = str(uuid.uuid4())[:8]
_anchor_backfill_status[backfill_id] = {"status": "starting"}
asyncio.create_task(_run_anchor_backfill(req, backfill_id))
return {
"status": "running",
"backfill_id": backfill_id,
"message": f"Anchor backfill started. Poll /generate/anchor-backfill-status/{backfill_id}",
}
@router.get("/generate/anchor-backfill-status/{backfill_id}")
async def get_anchor_backfill_status(backfill_id: str):
"""Get status of an anchor backfill job."""
status = _anchor_backfill_status.get(backfill_id)
if not status:
raise HTTPException(status_code=404, detail="Anchor backfill job not found")
return status
+144
View File
@@ -0,0 +1,144 @@
#!/usr/bin/env python3
"""
Migrate generation_metadata from TEXT (Python dict repr) to valid JSON.
Converts single quotes, None, True, False to JSON equivalents.
Run this BEFORE altering the column type to JSONB.
Usage:
python3 migrate_jsonb.py [--dry-run] [--batch-size 1000]
"""
import ast
import json
import logging
import os
import sys
from sqlalchemy import create_engine, text
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s")
logger = logging.getLogger(__name__)
DATABASE_URL = os.getenv(
"DATABASE_URL",
"postgresql://breakpilot:breakpilot123@localhost:5432/breakpilot_db",
)
BATCH_SIZE = int(sys.argv[sys.argv.index("--batch-size") + 1]) if "--batch-size" in sys.argv else 1000
DRY_RUN = "--dry-run" in sys.argv
def convert_python_dict_to_json(text_value: str) -> str:
"""Convert Python dict repr to valid JSON string."""
# Try ast.literal_eval first (handles single quotes, None, True, False)
try:
parsed = ast.literal_eval(text_value)
return json.dumps(parsed, ensure_ascii=False)
except (ValueError, SyntaxError):
pass
# Already valid JSON?
try:
json.loads(text_value)
return text_value
except (json.JSONDecodeError, ValueError):
pass
# Manual replacement as fallback
try:
fixed = text_value
fixed = fixed.replace("'", '"')
fixed = fixed.replace("None", "null")
fixed = fixed.replace("True", "true")
fixed = fixed.replace("False", "false")
json.loads(fixed) # Validate
return fixed
except (json.JSONDecodeError, ValueError):
pass
return None
def main():
engine = create_engine(DATABASE_URL)
with engine.connect() as conn:
conn.execute(text("SET search_path TO compliance, core, public"))
# Count rows needing conversion
total = conn.execute(text("""
SELECT COUNT(*) FROM canonical_controls
WHERE generation_metadata IS NOT NULL
AND generation_metadata != ''
AND LEFT(generation_metadata, 2) != '{"'
""")).scalar()
logger.info("Found %d rows with Python dict format (need conversion)", total)
logger.info("Dry run: %s, Batch size: %d", DRY_RUN, BATCH_SIZE)
if total == 0:
logger.info("Nothing to convert!")
return
converted = 0
failed = 0
offset = 0
while offset < total + BATCH_SIZE:
rows = conn.execute(text("""
SELECT id, generation_metadata FROM canonical_controls
WHERE generation_metadata IS NOT NULL
AND generation_metadata != ''
AND LEFT(generation_metadata, 2) != '{"'
ORDER BY id
LIMIT :batch
"""), {"batch": BATCH_SIZE}).fetchall()
if not rows:
break
for row in rows:
result = convert_python_dict_to_json(row.generation_metadata)
if result is None:
failed += 1
logger.warning("FAILED id=%s: %s", row.id, row.generation_metadata[:100])
# Set to empty JSON object so ALTER TABLE doesn't fail
if not DRY_RUN:
conn.execute(text("""
UPDATE canonical_controls
SET generation_metadata = '{}'
WHERE id = :id
"""), {"id": row.id})
else:
converted += 1
if not DRY_RUN:
conn.execute(text("""
UPDATE canonical_controls
SET generation_metadata = :val
WHERE id = :id
"""), {"id": row.id, "val": result})
if not DRY_RUN:
conn.commit()
offset += len(rows)
logger.info("Progress: %d/%d converted, %d failed", converted, total, failed)
# Also set empty strings to NULL
if not DRY_RUN:
nulled = conn.execute(text("""
UPDATE canonical_controls
SET generation_metadata = NULL
WHERE generation_metadata = ''
""")).rowcount
conn.commit()
logger.info("Set %d empty strings to NULL", nulled)
logger.info("DONE: %d converted, %d failed out of %d total", converted, failed, total)
if not DRY_RUN and failed == 0:
logger.info("All rows are now valid JSON. Safe to ALTER COLUMN to JSONB.")
if __name__ == "__main__":
main()
+86 -20
View File
@@ -2,19 +2,19 @@
Anchor Finder — finds open-source references (OWASP, NIST, ENISA) for controls.
Two-stage search:
Stage A: RAG-internal search for open-source chunks matching the control topic
Stage A: Direct Qdrant vector search for open-source chunks matching the control topic
Stage B: Web search via DuckDuckGo Instant Answer API (no API key needed)
Only open-source references (Rule 1+2) are accepted as anchors.
"""
import logging
import os
from dataclasses import dataclass
from typing import List, Optional
import httpx
from .rag_client import ComplianceRAGClient, get_rag_client
from .control_generator import (
GeneratedControl,
REGULATION_LICENSE_MAP,
@@ -25,9 +25,15 @@ from .control_generator import (
logger = logging.getLogger(__name__)
QDRANT_URL = os.getenv("QDRANT_URL", "http://qdrant:6333")
EMBEDDING_URL = os.getenv("EMBEDDING_URL", "http://embedding-service:8087")
# Regulation codes that are safe to reference as open anchors (Rule 1+2)
_OPEN_SOURCE_RULES = {1, 2}
# Collections to search for anchors (open-source frameworks)
_ANCHOR_COLLECTIONS = ["bp_compliance_ce", "bp_compliance_datenschutz"]
@dataclass
class OpenAnchor:
@@ -39,8 +45,9 @@ class OpenAnchor:
class AnchorFinder:
"""Finds open-source references to anchor generated controls."""
def __init__(self, rag_client: Optional[ComplianceRAGClient] = None):
self.rag = rag_client or get_rag_client()
def __init__(self, rag_client=None):
# rag_client kept for backwards compat but no longer used
pass
async def find_anchors(
self,
@@ -49,8 +56,8 @@ class AnchorFinder:
min_anchors: int = 2,
) -> List[OpenAnchor]:
"""Find open-source anchors for a control."""
# Stage A: RAG-internal search
anchors = await self._search_rag_for_open_anchors(control)
# Stage A: Direct Qdrant vector search
anchors = await self._search_qdrant_for_open_anchors(control)
# Stage B: Web search if not enough anchors
if len(anchors) < min_anchors and not skip_web:
@@ -63,39 +70,95 @@ class AnchorFinder:
return anchors
async def _search_rag_for_open_anchors(self, control: GeneratedControl) -> List[OpenAnchor]:
"""Search RAG for chunks from open sources matching the control topic."""
async def _get_embedding(self, text: str) -> list:
"""Get embedding vector via embedding service."""
try:
async with httpx.AsyncClient(timeout=10.0) as client:
resp = await client.post(
f"{EMBEDDING_URL}/embed",
json={"texts": [text]},
)
resp.raise_for_status()
embeddings = resp.json().get("embeddings", [])
return embeddings[0] if embeddings else []
except Exception as e:
logger.warning("Embedding request failed: %s", e)
return []
async def _search_qdrant_for_open_anchors(self, control: GeneratedControl) -> List[OpenAnchor]:
"""Search Qdrant directly for chunks from open sources matching the control topic."""
# Build search query from control title + first 3 tags
tags_str = " ".join(control.tags[:3]) if control.tags else ""
query = f"{control.title} {tags_str}".strip()
results = await self.rag.search_with_rerank(
query=query,
collection="bp_compliance_ce",
top_k=15,
)
# Get embedding for query
embedding = await self._get_embedding(query)
if not embedding:
return []
anchors: List[OpenAnchor] = []
seen: set[str] = set()
for r in results:
if not r.regulation_code:
for collection in _ANCHOR_COLLECTIONS:
try:
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.post(
f"{QDRANT_URL}/collections/{collection}/points/search",
json={
"vector": embedding,
"limit": 20,
"with_payload": True,
"with_vector": False,
},
)
if resp.status_code != 200:
logger.warning("Qdrant search %s failed: %d", collection, resp.status_code)
continue
results = resp.json().get("result", [])
except Exception as e:
logger.warning("Qdrant search error for %s: %s", collection, e)
continue
for hit in results:
payload = hit.get("payload", {})
# Qdrant payloads use regulation_id (not regulation_code)
regulation_code = (
payload.get("regulation_id", "")
or payload.get("regulation_code", "")
or payload.get("metadata", {}).get("regulation_id", "")
)
if not regulation_code:
continue
# Only accept open-source references
license_info = _classify_regulation(r.regulation_code)
license_info = _classify_regulation(regulation_code)
if license_info.get("rule") not in _OPEN_SOURCE_RULES:
continue
# Build reference key for dedup
ref = r.article or r.category or ""
key = f"{r.regulation_code}:{ref}"
article = payload.get("article", "") or payload.get("category", "") or ""
ref = article
key = f"{regulation_code}:{ref}"
if key in seen:
continue
seen.add(key)
framework_name = license_info.get("name", r.regulation_name or r.regulation_short or r.regulation_code)
url = r.source_url or self._build_reference_url(r.regulation_code, ref)
reg_name = (
payload.get("regulation_name_de", "")
or payload.get("regulation_name_en", "")
or payload.get("guideline_name", "")
)
reg_short = payload.get("regulation_short", "")
source_url = (
payload.get("download_url", "")
or payload.get("source_url", "")
or payload.get("source", "")
)
framework_name = license_info.get("name", reg_name or reg_short or regulation_code)
url = source_url or self._build_reference_url(regulation_code, ref)
anchors.append(OpenAnchor(
framework=framework_name,
@@ -106,6 +169,9 @@ class AnchorFinder:
if len(anchors) >= 5:
break
if len(anchors) >= 5:
break
return anchors
async def _search_web(self, control: GeneratedControl) -> List[OpenAnchor]:
+137 -25
View File
@@ -489,7 +489,7 @@ class GeneratorConfig(BaseModel):
max_controls: int = 0 # 0 = unlimited (process ALL chunks)
max_chunks: int = 0 # 0 = unlimited; >0 = stop after N chunks (respects document boundaries)
skip_processed: bool = True
skip_web_search: bool = False
skip_web_search: bool = True # Default True — Anchor-Search verlangsamt 5x, nachtraeglich batchen
dry_run: bool = False
existing_job_id: Optional[str] = None # If set, reuse this job instead of creating a new one
regulation_filter: Optional[List[str]] = None # Only process chunks matching these regulation_code prefixes
@@ -544,6 +544,8 @@ class GeneratorResult:
controls_too_close: int = 0
controls_duplicates_found: int = 0
controls_qa_fixed: int = 0
controls_stored: int = 0 # Actually persisted to DB
controls_store_failed: int = 0 # Generated but failed to persist
chunks_skipped_prefilter: int = 0
errors: list = field(default_factory=list)
controls: list = field(default_factory=list)
@@ -645,6 +647,13 @@ async def _llm_anthropic(prompt: str, system_prompt: Optional[str] = None, max_r
json=payload,
)
if resp.status_code != 200:
# Retry on transient HTTP errors
if resp.status_code in (429, 500, 502, 503, 504) and attempt < max_retries:
wait = 2 ** attempt
logger.warning("Anthropic API %d (transient) — retry in %ds...", resp.status_code, wait)
import asyncio
await asyncio.sleep(wait)
continue
logger.error("Anthropic API %d: %s", resp.status_code, resp.text[:300])
return ""
data = resp.json()
@@ -1517,9 +1526,22 @@ Gib ein JSON-Array zurueck mit GENAU {len(chunks)} Elementen. Fuer Aspekte ohne
final: list[Optional[GeneratedControl]] = []
for i in range(len(batch_items)):
control = all_controls.get(i)
if not control or (not control.title and not control.objective):
# Filter empty or invalid controls (LLM returned None/empty)
if not control:
final.append(None)
continue
title_invalid = not control.title or control.title.strip().lower() in ("none", "null", "")
obj_invalid = not control.objective or control.objective.strip().lower() in ("none", "null", "")
if title_invalid and obj_invalid:
logger.warning("Leerer Control gefiltert (title=%s, objective=%s) — wird nicht gespeichert",
control.title, control.objective)
final.append(None)
continue
# Clean up "None" strings from LLM
if title_invalid:
control.title = control.objective[:120] if control.objective else "Unbenannt"
if obj_invalid:
control.objective = control.title
if control.release_state == "too_close":
final.append(control)
@@ -1533,11 +1555,12 @@ Gib ein JSON-Array zurueck mit GENAU {len(chunks)} Elementen. Fuer Aspekte ohne
final.append(control)
continue
# Anchor search
# Anchor search — skip entirely when skip_web_search=true (backfilled later)
if not config.skip_web_search:
try:
from .anchor_finder import AnchorFinder
finder = AnchorFinder(self.rag)
anchors = await finder.find_anchors(control, skip_web=config.skip_web_search)
anchors = await finder.find_anchors(control, skip_web=False)
control.open_anchors = [asdict(a) if hasattr(a, '__dataclass_fields__') else a for a in anchors]
except Exception as e:
logger.warning("Anchor search failed: %s", e)
@@ -1732,20 +1755,52 @@ Gib ein JSON-Array zurueck mit GENAU {len(chunks)} Elementen. Fuer Aspekte ohne
)
def _generate_control_id(self, domain: str, db: Session) -> str:
"""Generate next sequential control ID like AUTH-011."""
"""Generate unique control ID using numeric MAX + collision guard.
Uses CAST to INTEGER for correct numeric ordering (not string sort).
Falls back to UUID suffix if collision is detected.
"""
prefix = domain.upper()[:4]
try:
# Numeric ordering — CAST to INTEGER, not string sort
result = db.execute(
text("SELECT control_id FROM canonical_controls WHERE control_id LIKE :prefix ORDER BY control_id DESC LIMIT 1"),
{"prefix": f"{prefix}-%"},
text("""
SELECT COALESCE(
MAX(CAST(SUBSTRING(control_id FROM :prefix_len) AS INTEGER)),
0
) + 1
FROM canonical_controls
WHERE control_id ~ (:pattern)
"""),
{"prefix_len": len(prefix) + 2, "pattern": f"^{prefix}-[0-9]+$"},
)
row = result.fetchone()
if row:
last_num = int(row[0].split("-")[-1])
return f"{prefix}-{last_num + 1:03d}"
except Exception:
pass
return f"{prefix}-001"
next_num = result.scalar() or 1
candidate = f"{prefix}-{next_num:03d}"
# Collision guard — check if ID already exists
exists = db.execute(
text("SELECT 1 FROM canonical_controls WHERE control_id = :cid LIMIT 1"),
{"cid": candidate},
).fetchone()
if exists:
# UUID suffix as fallback for race conditions
suffix = uuid.uuid4().hex[:6]
candidate = f"{prefix}-{next_num:03d}-{suffix}"
logger.warning(
"ID collision for %s-%03d — using unique suffix: %s",
prefix, next_num, candidate,
)
return candidate
except Exception as e:
# NEVER swallow silently — UUID as safe fallback
fallback = f"{prefix}-{uuid.uuid4().hex[:8]}"
logger.error(
"Failed to generate control_id for domain %s: %s — using fallback %s",
domain, e, fallback,
)
return fallback
# ── Stage QA: Automated Quality Validation ───────────────────────
@@ -1890,6 +1945,14 @@ Kategorien: {CATEGORY_LIST_STR}"""
def _store_control(self, control: GeneratedControl, job_id: str) -> Optional[str]:
"""Persist a generated control to DB. Returns the control UUID or None."""
# Pre-store quality guard — reject empty/invalid controls
if not control.title or control.title.strip().lower() in ("none", "null", ""):
logger.warning("Rejected control with empty/None title: %s", control.control_id)
return None
if not control.objective or control.objective.strip().lower() in ("none", "null", ""):
logger.warning("Rejected control with empty/None objective: %s%s", control.control_id, control.title)
return None
try:
# Get framework UUID
fw_result = self.db.execute(
@@ -1929,7 +1992,11 @@ Kategorien: {CATEGORY_LIST_STR}"""
:target_audience, :pipeline_version,
:applicable_industries, :applicable_company_size, :scope_conditions
)
ON CONFLICT (framework_id, control_id) DO NOTHING
ON CONFLICT (framework_id, control_id) DO UPDATE SET
updated_at = NOW(),
title = EXCLUDED.title,
objective = EXCLUDED.objective,
generation_metadata = EXCLUDED.generation_metadata
RETURNING id
"""),
{
@@ -2169,12 +2236,21 @@ Kategorien: {CATEGORY_LIST_STR}"""
if ctrl_uuid:
path = control.generation_metadata.get("processing_path", "structured_batch")
self._mark_chunk_processed(chunk, lic_info, path, [ctrl_uuid], job_id)
else:
self._mark_chunk_processed(chunk, lic_info, "store_failed", [], job_id)
result.controls_generated += 1
result.controls.append(asdict(control))
result.controls_stored += 1
controls_count += 1
else:
# CRITICAL FIX: Do NOT mark chunk as processed — allow retry
logger.error(
"STORE_FAILED: Control '%s' (%s) nicht gespeichert — Chunk bleibt unverarbeitet fuer Retry",
control.control_id, control.title[:60],
)
result.controls_store_failed += 1
else:
result.controls_generated += 1
controls_count += 1
result.controls.append(asdict(control))
if self._existing_controls is not None:
self._existing_controls.append({
@@ -2187,9 +2263,17 @@ Kategorien: {CATEGORY_LIST_STR}"""
try:
# Progress logging every 50 chunks
if i > 0 and i % 50 == 0:
store_rate = (result.controls_stored / max(result.controls_generated, 1)) * 100 if result.controls_generated > 0 else 100
logger.info(
"Progress: %d/%d chunks processed, %d controls generated, %d skipped by prefilter",
i, len(chunks), controls_count, chunks_skipped_prefilter,
"Progress: %d/%d chunks | %d generated | %d stored (%.0f%%) | %d store_failed | %d skipped",
i, len(chunks), result.controls_generated, result.controls_stored,
store_rate, result.controls_store_failed, chunks_skipped_prefilter,
)
# ALARM bei niedriger Store-Rate
if result.controls_generated > 10 and store_rate < 80:
logger.error(
"ALARM: Store-Erfolgsrate nur %.0f%% — moeglicherweise ID-Kollisionen!",
store_rate,
)
self._update_job(job_id, result)
@@ -2235,9 +2319,36 @@ Kategorien: {CATEGORY_LIST_STR}"""
await _flush_batch()
result.chunks_skipped_prefilter = chunks_skipped_prefilter
# Post-Job Validierung — DB-Realitaet pruefen
try:
actual_stored = self.db.execute(
text("SELECT count(*) FROM canonical_controls WHERE generation_metadata::text LIKE :jid"),
{"jid": f"%{job_id}%"},
).scalar() or 0
except Exception:
actual_stored = -1
final_store_rate = (result.controls_stored / max(result.controls_generated, 1)) * 100 if result.controls_generated > 0 else 0
logger.info(
"Pipeline complete: %d controls generated, %d chunks skipped by prefilter, %d total chunks",
controls_count, chunks_skipped_prefilter, len(chunks),
"Pipeline complete: %d chunks | %d generated | %d stored (%.0f%%) | %d store_failed | %d skipped | DB actual: %d",
len(chunks), result.controls_generated, result.controls_stored,
final_store_rate, result.controls_store_failed,
chunks_skipped_prefilter, actual_stored,
)
if result.controls_store_failed > 0:
logger.error(
"WARNUNG: %d Controls konnten NICHT gespeichert werden! "
"Diese Chunks bleiben unverarbeitet und muessen erneut verarbeitet werden.",
result.controls_store_failed,
)
if result.controls_generated > 0 and final_store_rate < 90:
logger.error(
"KRITISCH: Store-Rate nur %.0f%%%d von %d Controls verloren!",
final_store_rate, result.controls_store_failed, result.controls_generated,
)
result.status = "completed"
@@ -2292,11 +2403,12 @@ Kategorien: {CATEGORY_LIST_STR}"""
control.generation_metadata["similar_controls"] = duplicates
return control
# Stage 5: Anchor Search (imported from anchor_finder)
# Stage 5: Anchor Search — skip entirely when skip_web_search=true (backfilled later)
if not config.skip_web_search:
try:
from .anchor_finder import AnchorFinder
finder = AnchorFinder(self.rag)
anchors = await finder.find_anchors(control, skip_web=config.skip_web_search)
anchors = await finder.find_anchors(control, skip_web=False)
control.open_anchors = [asdict(a) if hasattr(a, '__dataclass_fields__') else a for a in anchors]
except Exception as e:
logger.warning("Anchor search failed: %s", e)
@@ -452,7 +452,7 @@ class MigrationPasses:
"review": """
UPDATE canonical_controls
SET generation_metadata = jsonb_set(
COALESCE(generation_metadata::jsonb, '{}'::jsonb),
COALESCE(generation_metadata, '{}'::jsonb),
'{triage_status}', '"review"'
)
WHERE release_state NOT IN ('deprecated')
@@ -462,7 +462,7 @@ class MigrationPasses:
"needs_obligation": """
UPDATE canonical_controls
SET generation_metadata = jsonb_set(
COALESCE(generation_metadata::jsonb, '{}'::jsonb),
COALESCE(generation_metadata, '{}'::jsonb),
'{triage_status}', '"needs_obligation"'
)
WHERE release_state NOT IN ('deprecated')
@@ -472,7 +472,7 @@ class MigrationPasses:
"needs_pattern": """
UPDATE canonical_controls
SET generation_metadata = jsonb_set(
COALESCE(generation_metadata::jsonb, '{}'::jsonb),
COALESCE(generation_metadata, '{}'::jsonb),
'{triage_status}', '"needs_pattern"'
)
WHERE release_state NOT IN ('deprecated')
@@ -482,7 +482,7 @@ class MigrationPasses:
"legacy_unlinked": """
UPDATE canonical_controls
SET generation_metadata = jsonb_set(
COALESCE(generation_metadata::jsonb, '{}'::jsonb),
COALESCE(generation_metadata, '{}'::jsonb),
'{triage_status}', '"legacy_unlinked"'
)
WHERE release_state NOT IN ('deprecated')
@@ -517,7 +517,7 @@ class MigrationPasses:
)
SELECT
COALESCE(
(generation_metadata::jsonb->>'source_regulation'),
(generation_metadata->>'source_regulation'),
''
) AS regulation_code,
obl.value::text AS obligation_id,
@@ -586,7 +586,7 @@ class MigrationPasses:
UPDATE canonical_controls
SET release_state = 'deprecated',
generation_metadata = jsonb_set(
COALESCE(generation_metadata::jsonb, '{}'::jsonb),
COALESCE(generation_metadata, '{}'::jsonb),
'{deprecated_reason}', '"duplicate_same_obligation_pattern"'
)
WHERE id = CAST(:uuid AS uuid)
+5 -2
View File
@@ -61,6 +61,7 @@ services:
- "3008:3008" # Admin Core
- "3010:3010" # Portal Dashboard
- "8011:8011" # Compliance Docs (MkDocs)
- "3012:3012" # Pitch Deck
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- vault_certs:/etc/nginx/certs:ro
@@ -873,11 +874,13 @@ services:
dockerfile: Dockerfile
container_name: bp-core-pitch-deck
platform: linux/arm64
ports:
- "3012:3000"
expose:
- "3000"
environment:
NODE_ENV: production
DATABASE_URL: postgres://${POSTGRES_USER:-breakpilot}:${POSTGRES_PASSWORD:-breakpilot123}@postgres:5432/${POSTGRES_DB:-breakpilot_db}
PITCH_JWT_SECRET: ${PITCH_JWT_SECRET:-7025f5da6d2ea384353ea6debddae0ea9e2dbca151a1df4b65be8cb80a5cf002}
PITCH_ADMIN_SECRET: ${PITCH_ADMIN_SECRET:-40df9e6f2ca2e90729030af37bf79199710b09c898cac9df}
LITELLM_URL: ${LITELLM_URL:-https://llm-dev.meghsakha.com}
LITELLM_MODEL: ${LITELLM_MODEL:-gpt-oss-120b}
LITELLM_API_KEY: ${LITELLM_API_KEY:-sk-0nAyxaMVbIqmz_ntnndzag}
+7 -7
View File
@@ -7,10 +7,10 @@ BreakPilot verwendet zwei Umgebungen:
```
┌─────────────────┐ ┌─────────────────┐
│ Development │───── git push ────▶│ Production │
│ (Mac Mini) │ │ (Coolify) │
│ (Mac Mini) │ │ (Orca) │
└─────────────────┘ └─────────────────┘
Lokale Automatisch
Entwicklung via Coolify
Entwicklung via Orca
```
## Umgebungen
@@ -32,21 +32,21 @@ BreakPilot verwendet zwei Umgebungen:
ssh macmini "cd ~/Projekte/breakpilot-core && /usr/local/bin/docker compose up -d"
```
### Production (Coolify)
### Production (Orca)
**Zweck:** Live-System
| Eigenschaft | Wert |
|-------------|------|
| Git Branch | `main` |
| Deployment | Coolify (automatisch bei Push auf gitea) |
| Deployment | Orca (automatisch bei Push auf gitea) |
| Database | Externe PostgreSQL (TLS) |
| Debug | Deaktiviert |
**Deploy:**
```bash
git push origin main && git push gitea main
# Coolify baut und deployt automatisch
# Orca baut und deployt automatisch
```
## Docker Compose Architektur
@@ -54,10 +54,10 @@ git push origin main && git push gitea main
```
docker-compose.yml ← Basis-Konfiguration (lokal, arm64)
└── docker-compose.coolify.yml ← Production Override (amd64)
└── docker-compose.orca.yml ← Production Override (amd64)
```
Coolify verwendet automatisch beide Compose-Files fuer den Production-Build.
Orca verwendet automatisch beide Compose-Files fuer den Production-Build.
## Secrets Management
+20 -20
View File
@@ -6,8 +6,8 @@ Uebersicht ueber den Deployment-Prozess fuer BreakPilot.
| Repo | Deployment | Trigger | Compose File |
|------|-----------|---------|--------------|
| **breakpilot-core** | Coolify (automatisch) | Push auf `coolify` Branch | `docker-compose.coolify.yml` |
| **breakpilot-compliance** | Coolify (automatisch) | Push auf `main` Branch | `docker-compose.yml` + `docker-compose.coolify.yml` |
| **breakpilot-core** | Orca (automatisch) | Push auf `orca` Branch | `docker-compose.orca.yml` |
| **breakpilot-compliance** | Orca (automatisch) | Push auf `main` Branch | `docker-compose.yml` + `docker-compose.orca.yml` |
| **breakpilot-lehrer** | Mac Mini (lokal) | Manuell `docker compose` | `docker-compose.yml` |
## Deployment-Architektur
@@ -16,7 +16,7 @@ Uebersicht ueber den Deployment-Prozess fuer BreakPilot.
┌─────────────────────────────────────────────────────────────────┐
│ Entwickler-MacBook │
│ │
│ breakpilot-core/ → git push gitea coolify
│ breakpilot-core/ → git push gitea orca
│ breakpilot-compliance/ → git push gitea main │
│ breakpilot-lehrer/ → git push + ssh macmini docker ... │
│ │
@@ -26,11 +26,11 @@ Uebersicht ueber den Deployment-Prozess fuer BreakPilot.
│ │
▼ ▼
┌───────────────────────────┐ ┌───────────────────────────┐
Coolify (Production) │ │ Mac Mini (Lokal/Dev) │
Orca (Production) │ │ Mac Mini (Lokal/Dev) │
│ │ │ │
│ Gitea Actions │ │ breakpilot-lehrer │
│ ├── Tests │ │ ├── studio-v2 │
│ └── Coolify API Deploy │ │ ├── klausur-service │
│ └── Orca API Deploy │ │ ├── klausur-service │
│ │ │ ├── backend-lehrer │
│ Core Services: │ │ └── voice-service │
│ ├── consent-service │ │ │
@@ -47,23 +47,23 @@ Uebersicht ueber den Deployment-Prozess fuer BreakPilot.
└───────────────────────────┘ └───────────────────────────┘
```
## breakpilot-core → Coolify
## breakpilot-core → Orca
### Pipeline
```yaml
# .gitea/workflows/deploy-coolify.yml
# .gitea/workflows/deploy-orca.yml
on:
push:
branches: [coolify]
branches: [orca]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy via Coolify API
# Triggert Coolify Build + Deploy ueber API
# Secrets: COOLIFY_API_TOKEN, COOLIFY_RESOURCE_UUID, COOLIFY_BASE_URL
- name: Deploy via Orca API
# Triggert Orca Build + Deploy ueber API
# Secrets: ORCA_API_TOKEN, ORCA_RESOURCE_UUID, ORCA_BASE_URL
```
### Workflow
@@ -74,13 +74,13 @@ jobs:
git push origin main && git push gitea main
# 3. Fuer Production-Deploy:
git push gitea coolify
git push gitea orca
# 4. Status pruefen:
# https://gitea.meghsakha.com/Benjamin_Boenisch/breakpilot-core/actions
```
### Coolify-deployed Services
### Orca-deployed Services
| Service | Container | Beschreibung |
|---------|-----------|--------------|
@@ -91,7 +91,7 @@ git push gitea coolify
| paddleocr-service | bp-core-paddleocr | OCR Engine (x86_64) |
| health-aggregator | bp-core-health | Health-Check Aggregator |
## breakpilot-compliance → Coolify
## breakpilot-compliance → Orca
### Pipeline
@@ -111,7 +111,7 @@ jobs:
### Workflow
```bash
# Committen und pushen → Coolify deployt automatisch:
# Committen und pushen → Orca deployt automatisch:
git push origin main && git push gitea main
# CI-Status pruefen:
@@ -154,8 +154,8 @@ Workflows liegen in jedem Repo unter `.gitea/workflows/`:
| Repo | Workflow | Branch | Aktion |
|------|----------|--------|--------|
| breakpilot-core | `deploy-coolify.yml` | `coolify` | Coolify API Deploy |
| breakpilot-compliance | `ci.yaml` | `main` | Tests + Coolify Deploy |
| breakpilot-core | `deploy-orca.yml` | `orca` | Orca API Deploy |
| breakpilot-compliance | `ci.yaml` | `main` | Tests + Orca Deploy |
### Runner-Token erneuern
@@ -181,7 +181,7 @@ ssh macmini "/usr/local/bin/docker logs -f bp-core-gitea-runner"
## Health Checks
### Production (Coolify)
### Production (Orca)
```bash
# Core PaddleOCR
@@ -229,14 +229,14 @@ ssh macmini "docker compose build --no-cache <service>"
## Rollback
### Coolify
### Orca
Ein Redeploy mit einem aelteren Commit kann durch Zuruecksetzen des Branches ausgeloest werden:
```bash
# Branch auf vorherigen Commit zuruecksetzen und pushen
git reset --hard <previous-commit>
git push gitea coolify --force
git push gitea orca --force
```
### Lokal (Mac Mini)
+2 -2
View File
@@ -16,8 +16,8 @@ BreakPilot besteht aus drei unabhaengigen Projekten:
| Repo | Deployment | Trigger |
|------|-----------|---------|
| **breakpilot-core** | Coolify (automatisch) | Push auf gitea main |
| **breakpilot-compliance** | Coolify (automatisch) | Push auf gitea main |
| **breakpilot-core** | Orca (automatisch) | Push auf gitea main |
| **breakpilot-compliance** | Orca (automatisch) | Push auf gitea main |
| **breakpilot-lehrer** | Mac Mini (lokal) | Manuell docker compose |
## Core Services
@@ -330,6 +330,14 @@ def _generate_risk_assessment(ctx: dict) -> str:
if any(ctx.get(k) for k in ["third_country_transfer", "processes_in_third_country"]):
risks.append(("Zugriff durch Behoerden in Drittlaendern", "mittel", "hoch", "hoch"))
# FISA 702 Risiko bei US-Cloud-Providern
hosting = (ctx.get("hosting_provider") or "").lower()
us_providers = ("aws", "azure", "google", "microsoft", "amazon", "openai", "anthropic", "oracle")
if any(p in hosting for p in us_providers):
risks.append(("FISA 702: Zugriff durch US-Behoerden auf EU-Daten nicht ausschliessbar", "mittel", "hoch", "hoch"))
risks.append(("EU-Serverstandort schuetzt nicht gegen US-Rechtszugriff (Cloud Act + FISA)", "mittel", "hoch", "hoch"))
risks.append(("Fehlende effektive Rechtsbehelfe fuer EU-Betroffene gegen US-Ueberwachung", "mittel", "hoch", "hoch"))
# Domain-spezifische Risiken (AI Act Annex III)
domain = ctx.get("domain", "")
if domain in ("hr", "recruiting") or ctx.get("has_hr_context"):
+30
View File
@@ -760,3 +760,33 @@ server {
try_files $uri $uri/ /index.html;
}
}
# =========================================================
# PITCH DECK: Investor Presentation on port 3012
# =========================================================
server {
listen 3012 ssl;
http2 on;
server_name macmini localhost;
ssl_certificate /etc/nginx/certs/macmini.crt;
ssl_certificate_key /etc/nginx/certs/macmini.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
location / {
set $upstream_pitch bp-core-pitch-deck:3000;
proxy_pass http://$upstream_pitch;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_read_timeout 300s;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
}
}
+4
View File
@@ -12,6 +12,10 @@ RUN npm install
# Copy source code
COPY . .
# Embed git commit hash into build
ARG GIT_SHA=dev
ENV GIT_SHA=$GIT_SHA
# Build the application
RUN npm run build
+7
View File
@@ -0,0 +1,7 @@
Tue Apr 14 09:22:10 AM CEST 2026
Tue Apr 14 09:27:05 AM CEST 2026
Tue Apr 14 09:32:36 AM CEST 2026
Tue Apr 15 rebuild trigger
Tue Apr 15 rebuild 2
@@ -0,0 +1,294 @@
/**
* Regression test for the "lost access" scenario:
*
* 1. Admin invites investor A → token T1 is created and emailed.
* 2. Investor A opens the link successfully → T1 is marked used_at.
* 3. Investor A clears their session (or a redeploy drops cookies).
* 4. Investor A returns to / — redirected to /auth.
* 5. Without this feature, A is stuck: T1 is already used, expired, or the
* session is gone, and there is no self-service way to get back in.
* 6. With this feature, A enters their email on /auth and the endpoint
* issues a brand new, unused magic link T2 for the same investor row.
*
* This test wires together the request-link handler with the real verify
* handler against an in-memory fake of the two tables the flow touches
* (pitch_investors, pitch_magic_links) so we can assert end-to-end that a
* second link works after the first one was used.
*/
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { NextRequest } from 'next/server'
// ---- In-memory fake of the two tables touched by this flow ----
interface InvestorRow {
id: string
email: string
name: string | null
company: string | null
status: 'invited' | 'active' | 'revoked'
last_login_at: Date | null
login_count: number
}
interface MagicLinkRow {
id: string
investor_id: string
token: string
expires_at: Date
used_at: Date | null
ip_address: string | null
user_agent: string | null
}
const db = {
investors: [] as InvestorRow[],
magicLinks: [] as MagicLinkRow[],
sessions: [] as { id: string; investor_id: string; ip_address: string | null }[],
}
let idCounter = 0
const nextId = () => `row-${++idCounter}`
// A tiny query router: match the SQL fragment we care about, ignore the rest.
const queryMock = vi.fn(async (sql: string, params: unknown[] = []) => {
const s = sql.replace(/\s+/g, ' ').trim()
// Investor lookup by email (used by request-link)
if (/SELECT id, email, name, status FROM pitch_investors WHERE email = \$1/i.test(s)) {
const row = db.investors.find(i => i.email === params[0])
return { rows: row ? [row] : [] }
}
// Insert magic link
if (/INSERT INTO pitch_magic_links \(investor_id, token, expires_at\)/i.test(s)) {
db.magicLinks.push({
id: nextId(),
investor_id: params[0] as string,
token: params[1] as string,
expires_at: params[2] as Date,
used_at: null,
ip_address: null,
user_agent: null,
})
return { rows: [] }
}
// Verify: magic link + investor JOIN lookup
if (/FROM pitch_magic_links ml JOIN pitch_investors i/i.test(s)) {
const link = db.magicLinks.find(ml => ml.token === params[0])
if (!link) return { rows: [] }
const inv = db.investors.find(i => i.id === link.investor_id)!
return {
rows: [{
id: link.id,
investor_id: link.investor_id,
expires_at: link.expires_at,
used_at: link.used_at,
email: inv.email,
investor_status: inv.status,
}],
}
}
// Mark magic link used
if (/UPDATE pitch_magic_links SET used_at = NOW/i.test(s)) {
const link = db.magicLinks.find(ml => ml.id === params[2])
if (link) {
link.used_at = new Date()
link.ip_address = params[0] as string | null
link.user_agent = params[1] as string | null
}
return { rows: [] }
}
// Activate investor
if (/UPDATE pitch_investors SET status = 'active'/i.test(s)) {
const inv = db.investors.find(i => i.id === params[0])
if (inv) {
inv.status = 'active'
inv.last_login_at = new Date()
inv.login_count += 1
}
return { rows: [] }
}
// createSession: revoke prior sessions (no-op in fake)
if (/UPDATE pitch_sessions SET revoked = true WHERE investor_id/i.test(s)) {
return { rows: [] }
}
// createSession: insert
if (/INSERT INTO pitch_sessions/i.test(s)) {
const id = nextId()
db.sessions.push({ id, investor_id: params[0] as string, ip_address: params[2] as string | null })
return { rows: [{ id }] }
}
// createSession: fetch investor email for JWT
if (/SELECT email FROM pitch_investors WHERE id = \$1/i.test(s)) {
const inv = db.investors.find(i => i.id === params[0])
return { rows: inv ? [{ email: inv.email }] : [] }
}
// new-ip detection query (verify route)
if (/SELECT DISTINCT ip_address FROM pitch_sessions/i.test(s)) {
return { rows: [] }
}
// Audit log insert — accept everything
if (/INSERT INTO pitch_audit_logs/i.test(s)) {
return { rows: [] }
}
throw new Error(`Unmocked query: ${s.slice(0, 120)}`)
})
vi.mock('@/lib/db', () => ({
default: { query: (...args: unknown[]) => queryMock(args[0] as string, args[1] as unknown[]) },
}))
// Capture emails instead of sending them
const sentEmails: Array<{ to: string; url: string }> = []
vi.mock('@/lib/email', () => ({
sendMagicLinkEmail: vi.fn(async (to: string, _name: string | null, url: string) => {
sentEmails.push({ to, url })
}),
}))
// next/headers cookies() needs to be stubbed — setSessionCookie calls it.
vi.mock('next/headers', () => ({
cookies: async () => ({
set: vi.fn(),
get: vi.fn(),
delete: vi.fn(),
}),
}))
// Import the handlers AFTER mocks are set up
import { POST as requestLink } from '@/app/api/auth/request-link/route'
import { POST as verifyLink } from '@/app/api/auth/verify/route'
function makeJsonRequest(url: string, body: unknown, ip = '203.0.113.1'): NextRequest {
return new NextRequest(url, {
method: 'POST',
headers: { 'content-type': 'application/json', 'x-forwarded-for': ip },
body: JSON.stringify(body),
})
}
function extractToken(url: string): string {
const m = url.match(/token=([0-9a-f]+)/)
if (!m) throw new Error(`No token in url: ${url}`)
return m[1]
}
beforeEach(() => {
db.investors = []
db.magicLinks = []
db.sessions = []
sentEmails.length = 0
idCounter = 0
queryMock.mockClear()
})
describe('Regression: investor can re-request a working magic link after the first is consumed', () => {
it('full flow — invite → use → request-link → new link works', async () => {
// --- Setup: admin has already invited the investor (simulate the outcome) ---
const investorId = 'investor-42'
db.investors.push({
id: investorId,
email: 'vc@example.com',
name: 'VC Partner',
company: 'Acme Capital',
status: 'invited',
last_login_at: null,
login_count: 0,
})
db.magicLinks.push({
id: 'ml-original',
investor_id: investorId,
token: 'a'.repeat(96), // original invite token
expires_at: new Date(Date.now() + 72 * 60 * 60 * 1000),
used_at: null,
ip_address: null,
user_agent: null,
})
// --- Step 1: investor uses the original invite link ---
const firstVerify = await verifyLink(makeJsonRequest('http://localhost/api/auth/verify', { token: 'a'.repeat(96) }))
expect(firstVerify.status).toBe(200)
const first = db.magicLinks.find(ml => ml.id === 'ml-original')!
expect(first.used_at).not.toBeNull()
// --- Step 2: investor comes back later; clicks the same link → rejected ---
const replay = await verifyLink(makeJsonRequest('http://localhost/api/auth/verify', { token: 'a'.repeat(96) }))
expect(replay.status).toBe(401)
const replayBody = await replay.json()
expect(replayBody.error).toMatch(/already been used/i)
// --- Step 3: investor visits /auth and submits their email ---
const reissue = await requestLink(
makeJsonRequest('http://localhost/api/auth/request-link', { email: 'vc@example.com' }, '203.0.113.99'),
)
expect(reissue.status).toBe(200)
const reissueBody = await reissue.json()
expect(reissueBody.success).toBe(true)
// --- Step 4: a fresh email was dispatched to the investor ---
expect(sentEmails).toHaveLength(1)
expect(sentEmails[0].to).toBe('vc@example.com')
const newToken = extractToken(sentEmails[0].url)
expect(newToken).not.toBe('a'.repeat(96))
expect(newToken).toMatch(/^[0-9a-f]{96}$/)
// A second unused magic link row exists for the same investor
const links = db.magicLinks.filter(ml => ml.investor_id === investorId)
expect(links).toHaveLength(2)
const newLink = links.find(ml => ml.token === newToken)!
expect(newLink.used_at).toBeNull()
// --- Step 5: the new token validates successfully ---
const secondVerify = await verifyLink(makeJsonRequest('http://localhost/api/auth/verify', { token: newToken }))
expect(secondVerify.status).toBe(200)
const secondBody = await secondVerify.json()
expect(secondBody.success).toBe(true)
expect(secondBody.redirect).toBe('/')
// And the new link is now used, mirroring the one-time-use contract
expect(newLink.used_at).not.toBeNull()
})
it('unknown emails do not create magic links or send email (prevents enumeration & abuse)', async () => {
// No investors in the DB
const res = await requestLink(
makeJsonRequest('http://localhost/api/auth/request-link', { email: 'stranger@example.com' }),
)
expect(res.status).toBe(200)
const body = await res.json()
// Same generic message as the happy path
expect(body.success).toBe(true)
expect(body.message).toMatch(/if this email was invited/i)
expect(sentEmails).toHaveLength(0)
expect(db.magicLinks).toHaveLength(0)
})
it('revoked investors cannot self-serve a new link', async () => {
db.investors.push({
id: 'revoked-1',
email: 'gone@example.com',
name: null,
company: null,
status: 'revoked',
last_login_at: null,
login_count: 0,
})
const res = await requestLink(
makeJsonRequest('http://localhost/api/auth/request-link', { email: 'gone@example.com' }),
)
expect(res.status).toBe(200) // generic success (no info leak)
expect(sentEmails).toHaveLength(0)
expect(db.magicLinks).toHaveLength(0)
})
})
@@ -0,0 +1,213 @@
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { NextRequest } from 'next/server'
// Mock the DB pool before the route is imported
const queryMock = vi.fn()
vi.mock('@/lib/db', () => ({
default: { query: (...args: unknown[]) => queryMock(...args) },
}))
// Mock the email sender so no SMTP is attempted
const sendMagicLinkEmailMock = vi.fn().mockResolvedValue(undefined)
vi.mock('@/lib/email', () => ({
sendMagicLinkEmail: (...args: unknown[]) => sendMagicLinkEmailMock(...args),
}))
// Import after mocks are registered
import { POST } from '@/app/api/auth/request-link/route'
// Unique suffix per test so the rate-limit store (keyed by IP / email) doesn't
// bleed across cases — the rate-limiter holds state at module scope.
let testId = 0
function uniqueIp() {
testId++
return `10.0.${Math.floor(testId / 250)}.${testId % 250}`
}
function makeRequest(body: unknown, ip = uniqueIp()): NextRequest {
return new NextRequest('http://localhost/api/auth/request-link', {
method: 'POST',
headers: {
'content-type': 'application/json',
'x-forwarded-for': ip,
},
body: JSON.stringify(body),
})
}
function investorRow(overrides: Partial<{ id: string; email: string; name: string | null; status: string }> = {}) {
return {
id: overrides.id ?? 'investor-1',
email: overrides.email ?? 'invited@example.com',
name: overrides.name ?? 'Alice',
status: overrides.status ?? 'invited',
}
}
beforeEach(() => {
queryMock.mockReset()
sendMagicLinkEmailMock.mockReset()
sendMagicLinkEmailMock.mockResolvedValue(undefined)
})
describe('POST /api/auth/request-link — input validation', () => {
it('returns 400 when email is missing', async () => {
const res = await POST(makeRequest({}))
expect(res.status).toBe(400)
const body = await res.json()
expect(body.error).toBe('Email required')
expect(queryMock).not.toHaveBeenCalled()
expect(sendMagicLinkEmailMock).not.toHaveBeenCalled()
})
it('returns 400 when email is not a string', async () => {
const res = await POST(makeRequest({ email: 12345 }))
expect(res.status).toBe(400)
expect(sendMagicLinkEmailMock).not.toHaveBeenCalled()
})
it('handles malformed JSON body as missing email (400)', async () => {
const req = new NextRequest('http://localhost/api/auth/request-link', {
method: 'POST',
headers: { 'content-type': 'application/json', 'x-forwarded-for': uniqueIp() },
body: 'not-json',
})
const res = await POST(req)
expect(res.status).toBe(400)
})
})
describe('POST /api/auth/request-link — unknown email (enumeration resistance)', () => {
it('returns the generic success response without sending email', async () => {
// First query: investor lookup → empty rows
queryMock.mockResolvedValueOnce({ rows: [] })
// Second query: the audit log insert
queryMock.mockResolvedValueOnce({ rows: [] })
const res = await POST(makeRequest({ email: 'unknown@example.com' }))
expect(res.status).toBe(200)
const body = await res.json()
expect(body.success).toBe(true)
expect(body.message).toMatch(/if this email was invited/i)
expect(sendMagicLinkEmailMock).not.toHaveBeenCalled()
// Verify the investor-lookup SQL was issued with the normalized email
const [sql, params] = queryMock.mock.calls[0]
expect(sql).toMatch(/FROM pitch_investors WHERE email/i)
expect(params).toEqual(['unknown@example.com'])
})
it('normalizes email (trim + lowercase) before lookup', async () => {
queryMock.mockResolvedValueOnce({ rows: [] })
queryMock.mockResolvedValueOnce({ rows: [] })
await POST(makeRequest({ email: ' Mixed@Example.COM ' }))
const [, params] = queryMock.mock.calls[0]
expect(params).toEqual(['mixed@example.com'])
})
})
describe('POST /api/auth/request-link — known investor', () => {
it('creates a new magic link and sends the email with generic response', async () => {
// 1st: investor lookup → found
queryMock.mockResolvedValueOnce({ rows: [investorRow()] })
// 2nd: magic link insert
queryMock.mockResolvedValueOnce({ rows: [] })
// 3rd: audit log insert
queryMock.mockResolvedValueOnce({ rows: [] })
const res = await POST(makeRequest({ email: 'invited@example.com' }))
expect(res.status).toBe(200)
const body = await res.json()
expect(body.success).toBe(true)
// Response is identical to the unknown-email case (no information leak)
expect(body.message).toMatch(/if this email was invited/i)
// Verify magic link insert
const [insertSql, insertParams] = queryMock.mock.calls[1]
expect(insertSql).toMatch(/INSERT INTO pitch_magic_links/i)
expect(insertParams[0]).toBe('investor-1')
expect(insertParams[1]).toMatch(/^[0-9a-f]{96}$/) // 96-char hex token
expect(insertParams[2]).toBeInstanceOf(Date)
// Verify email was sent with the fresh token URL
expect(sendMagicLinkEmailMock).toHaveBeenCalledTimes(1)
const [emailTo, emailName, magicLinkUrl] = sendMagicLinkEmailMock.mock.calls[0]
expect(emailTo).toBe('invited@example.com')
expect(emailName).toBe('Alice')
expect(magicLinkUrl).toMatch(/\/auth\/verify\?token=[0-9a-f]{96}$/)
})
it('generates a different token on each call (re-invite is always fresh)', async () => {
// Call 1
queryMock.mockResolvedValueOnce({ rows: [investorRow({ email: 'a@x.com' })] })
queryMock.mockResolvedValueOnce({ rows: [] })
queryMock.mockResolvedValueOnce({ rows: [] })
await POST(makeRequest({ email: 'a@x.com' }))
// Call 2 — different email to avoid the per-email rate limit
queryMock.mockResolvedValueOnce({ rows: [investorRow({ email: 'b@x.com' })] })
queryMock.mockResolvedValueOnce({ rows: [] })
queryMock.mockResolvedValueOnce({ rows: [] })
await POST(makeRequest({ email: 'b@x.com' }))
const token1 = queryMock.mock.calls[1][1][1]
const token2 = queryMock.mock.calls[4][1][1]
expect(token1).not.toBe(token2)
})
it('skips email send for a revoked investor (returns generic response)', async () => {
queryMock.mockResolvedValueOnce({ rows: [investorRow({ status: 'revoked' })] })
queryMock.mockResolvedValueOnce({ rows: [] }) // audit log
const res = await POST(makeRequest({ email: 'invited@example.com' }))
expect(res.status).toBe(200)
const body = await res.json()
expect(body.success).toBe(true)
expect(sendMagicLinkEmailMock).not.toHaveBeenCalled()
// Ensure no magic link was inserted
const inserts = queryMock.mock.calls.filter(c => /INSERT INTO pitch_magic_links/i.test(c[0]))
expect(inserts.length).toBe(0)
})
})
describe('POST /api/auth/request-link — rate limiting', () => {
it('throttles after N requests per email and returns generic success (silent throttle)', async () => {
const email = `throttle-${Date.now()}@example.com`
// First 3 requests succeed (RATE_LIMITS.magicLink.limit = 3)
for (let i = 0; i < 3; i++) {
queryMock.mockResolvedValueOnce({ rows: [investorRow({ email })] })
queryMock.mockResolvedValueOnce({ rows: [] }) // magic link insert
queryMock.mockResolvedValueOnce({ rows: [] }) // audit log
const res = await POST(makeRequest({ email }))
expect(res.status).toBe(200)
}
expect(sendMagicLinkEmailMock).toHaveBeenCalledTimes(3)
// 4th request is silently throttled — same generic response, no email sent
queryMock.mockResolvedValueOnce({ rows: [] }) // audit log only
const res4 = await POST(makeRequest({ email }))
expect(res4.status).toBe(200)
const body4 = await res4.json()
expect(body4.success).toBe(true)
// Still exactly 3 emails sent — nothing new
expect(sendMagicLinkEmailMock).toHaveBeenCalledTimes(3)
})
it('throttles with 429 after too many attempts from the same IP', async () => {
const ip = '172.31.99.99'
// RATE_LIMITS.authVerify.limit = 10 for IP-scoped checks
for (let i = 0; i < 10; i++) {
queryMock.mockResolvedValueOnce({ rows: [] }) // investor lookup returns empty
queryMock.mockResolvedValueOnce({ rows: [] }) // audit
const res = await POST(makeRequest({ email: `ip-test-${i}@example.com` }, ip))
expect(res.status).toBe(200)
}
const res = await POST(makeRequest({ email: 'final@example.com' }, ip))
expect(res.status).toBe(429)
})
})
@@ -0,0 +1,17 @@
import { NextRequest, NextResponse } from 'next/server'
import { requireAdmin } from '@/lib/admin-auth'
import pool from '@/lib/db'
import { computeFinanzplan } from '@/lib/finanzplan/engine'
/** Admin-only: recompute a Finanzplan scenario. */
export async function POST(request: NextRequest) {
const guard = await requireAdmin(request)
if (guard.kind === 'response') return guard.response
const body = await request.json().catch(() => ({}))
const scenarioId = body.scenarioId || (await pool.query("SELECT id FROM fp_scenarios WHERE is_default = true LIMIT 1")).rows[0]?.id
if (!scenarioId) return NextResponse.json({ error: 'No scenario found' }, { status: 404 })
const result = await computeFinanzplan(pool, scenarioId)
return NextResponse.json({ success: true, scenarioId, cash_m60: result.liquiditaet?.endstand?.m60 })
}
@@ -0,0 +1,63 @@
import { NextRequest, NextResponse } from 'next/server'
import { requireAdmin } from '@/lib/admin-auth'
import pool from '@/lib/db'
// POST: Import finanzplan data (all fp_* tables) from JSON dump
export async function POST(request: NextRequest) {
const guard = await requireAdmin(request)
if (guard.kind === 'response') return guard.response
try {
const data = await request.json()
const results: string[] = []
const client = await pool.connect()
try {
await client.query('BEGIN')
const tables = [
'fp_scenarios', 'fp_kunden', 'fp_kunden_summary', 'fp_umsatzerloese',
'fp_materialaufwand', 'fp_personalkosten', 'fp_betriebliche_aufwendungen',
'fp_investitionen', 'fp_sonst_ertraege', 'fp_liquiditaet', 'fp_guv',
]
for (const table of tables) {
const rows = data[table]
if (!rows || !Array.isArray(rows) || rows.length === 0) {
results.push(`SKIP: ${table} (no data)`)
continue
}
// Clear existing data
await client.query(`DELETE FROM ${table}`)
// Insert rows
const cols = Object.keys(rows[0])
const colNames = cols.join(', ')
for (const row of rows) {
const values = cols.map(c => {
const v = row[c]
if (v === null || v === undefined) return null
if (typeof v === 'object') return JSON.stringify(v)
return v
})
const placeholders = values.map((_, i) => `$${i + 1}`).join(', ')
await client.query(`INSERT INTO ${table} (${colNames}) VALUES (${placeholders})`, values)
}
results.push(`OK: ${table}${rows.length} rows`)
}
await client.query('COMMIT')
return NextResponse.json({ success: true, results })
} catch (err) {
await client.query('ROLLBACK')
throw err
} finally {
client.release()
}
} catch (error) {
return NextResponse.json({ error: String(error) }, { status: 500 })
}
}
+2 -2
View File
@@ -11,7 +11,7 @@ export async function POST(request: NextRequest) {
const adminId = guard.kind === 'admin' ? guard.admin.id : null
const body = await request.json().catch(() => ({}))
const { email, name, company } = body
const { email, name, company, greeting, message, closing } = body
if (!email || typeof email !== 'string') {
return NextResponse.json({ error: 'Email required' }, { status: 400 })
@@ -54,7 +54,7 @@ export async function POST(request: NextRequest) {
const baseUrl = process.env.PITCH_BASE_URL || 'https://pitch.breakpilot.ai'
const magicLinkUrl = `${baseUrl}/auth/verify?token=${token}`
await sendMagicLinkEmail(normalizedEmail, name || null, magicLinkUrl)
await sendMagicLinkEmail(normalizedEmail, name || null, magicLinkUrl, greeting, message, closing)
await logAdminAudit(
adminId,
+128
View File
@@ -0,0 +1,128 @@
import { NextRequest, NextResponse } from 'next/server'
import { requireAdmin } from '@/lib/admin-auth'
import pool from '@/lib/db'
export async function POST(request: NextRequest) {
const guard = await requireAdmin(request)
if (guard.kind === 'response') return guard.response
const results: string[] = []
// Finanzplan tables — the ones missing on production
const statements = [
`CREATE TABLE IF NOT EXISTS fp_scenarios (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL DEFAULT 'Base Case',
description TEXT,
is_default BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
)`,
`INSERT INTO fp_scenarios (name, description, is_default)
SELECT 'Base Case', 'Basisdaten aus Excel-Import', true
WHERE NOT EXISTS (SELECT 1 FROM fp_scenarios WHERE is_default = true)`,
`CREATE TABLE IF NOT EXISTS fp_kunden (
id SERIAL PRIMARY KEY, scenario_id UUID REFERENCES fp_scenarios(id) ON DELETE CASCADE,
segment_name TEXT NOT NULL, segment_index INT NOT NULL, row_label TEXT NOT NULL, row_index INT NOT NULL,
percentage NUMERIC(5,3), formula_type TEXT, is_editable BOOLEAN DEFAULT false,
values JSONB NOT NULL DEFAULT '{}', excel_row INT, sort_order INT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW()
)`,
`CREATE TABLE IF NOT EXISTS fp_kunden_summary (
id SERIAL PRIMARY KEY, scenario_id UUID REFERENCES fp_scenarios(id) ON DELETE CASCADE,
row_label TEXT NOT NULL, row_index INT NOT NULL, values JSONB NOT NULL DEFAULT '{}',
excel_row INT, sort_order INT NOT NULL
)`,
`CREATE TABLE IF NOT EXISTS fp_umsatzerloese (
id SERIAL PRIMARY KEY, scenario_id UUID REFERENCES fp_scenarios(id) ON DELETE CASCADE,
section TEXT NOT NULL, row_label TEXT NOT NULL, row_index INT NOT NULL,
is_editable BOOLEAN DEFAULT false, values JSONB NOT NULL DEFAULT '{}',
excel_row INT, sort_order INT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW()
)`,
`CREATE TABLE IF NOT EXISTS fp_materialaufwand (
id SERIAL PRIMARY KEY, scenario_id UUID REFERENCES fp_scenarios(id) ON DELETE CASCADE,
section TEXT NOT NULL, row_label TEXT NOT NULL, row_index INT NOT NULL,
is_editable BOOLEAN DEFAULT false, values JSONB NOT NULL DEFAULT '{}',
excel_row INT, sort_order INT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW()
)`,
`CREATE TABLE IF NOT EXISTS fp_personalkosten (
id SERIAL PRIMARY KEY, scenario_id UUID REFERENCES fp_scenarios(id) ON DELETE CASCADE,
person_name TEXT NOT NULL, person_nr TEXT, position TEXT,
start_date DATE, end_date DATE, brutto_monthly NUMERIC(10,2),
annual_raise_pct NUMERIC(5,2) DEFAULT 3.0, ag_sozial_pct NUMERIC(5,2) DEFAULT 20.425,
is_editable BOOLEAN DEFAULT true,
values_brutto JSONB NOT NULL DEFAULT '{}', values_sozial JSONB NOT NULL DEFAULT '{}',
values_total JSONB NOT NULL DEFAULT '{}',
excel_row INT, sort_order INT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW()
)`,
`CREATE TABLE IF NOT EXISTS fp_betriebliche_aufwendungen (
id SERIAL PRIMARY KEY, scenario_id UUID REFERENCES fp_scenarios(id) ON DELETE CASCADE,
category TEXT NOT NULL, row_label TEXT NOT NULL, row_index INT NOT NULL,
is_editable BOOLEAN DEFAULT true, is_sum_row BOOLEAN DEFAULT false, formula_desc TEXT,
values JSONB NOT NULL DEFAULT '{}', excel_row INT, sort_order INT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW()
)`,
`CREATE TABLE IF NOT EXISTS fp_investitionen (
id SERIAL PRIMARY KEY, scenario_id UUID REFERENCES fp_scenarios(id) ON DELETE CASCADE,
item_name TEXT NOT NULL, category TEXT, purchase_amount NUMERIC(12,2) NOT NULL,
purchase_date DATE, afa_years INT, afa_end_date DATE, is_editable BOOLEAN DEFAULT true,
values_invest JSONB NOT NULL DEFAULT '{}', values_afa JSONB NOT NULL DEFAULT '{}',
excel_row INT, sort_order INT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW()
)`,
`CREATE TABLE IF NOT EXISTS fp_sonst_ertraege (
id SERIAL PRIMARY KEY, scenario_id UUID REFERENCES fp_scenarios(id) ON DELETE CASCADE,
category TEXT NOT NULL, row_label TEXT, row_index INT NOT NULL,
is_editable BOOLEAN DEFAULT true, is_sum_row BOOLEAN DEFAULT false,
values JSONB NOT NULL DEFAULT '{}', excel_row INT, sort_order INT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW()
)`,
`CREATE TABLE IF NOT EXISTS fp_liquiditaet (
id SERIAL PRIMARY KEY, scenario_id UUID REFERENCES fp_scenarios(id) ON DELETE CASCADE,
row_label TEXT NOT NULL, row_type TEXT NOT NULL,
is_editable BOOLEAN DEFAULT false, formula_desc TEXT,
values JSONB NOT NULL DEFAULT '{}', excel_row INT, sort_order INT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW()
)`,
`CREATE TABLE IF NOT EXISTS fp_guv (
id SERIAL PRIMARY KEY, scenario_id UUID REFERENCES fp_scenarios(id) ON DELETE CASCADE,
row_label TEXT NOT NULL, row_index INT NOT NULL,
is_sum_row BOOLEAN DEFAULT false, formula_desc TEXT,
values JSONB NOT NULL DEFAULT '{}', excel_row INT, sort_order INT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW()
)`,
`CREATE TABLE IF NOT EXISTS fp_cell_overrides (
id SERIAL PRIMARY KEY, scenario_id UUID REFERENCES fp_scenarios(id) ON DELETE CASCADE,
sheet_name TEXT NOT NULL, row_id INT NOT NULL, month_key TEXT NOT NULL,
override_value NUMERIC, created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(scenario_id, sheet_name, row_id, month_key)
)`,
`CREATE INDEX IF NOT EXISTS idx_fp_kunden_scenario ON fp_kunden(scenario_id)`,
`CREATE INDEX IF NOT EXISTS idx_fp_kunden_summary_scenario ON fp_kunden_summary(scenario_id)`,
`CREATE INDEX IF NOT EXISTS idx_fp_umsatz_scenario ON fp_umsatzerloese(scenario_id)`,
`CREATE INDEX IF NOT EXISTS idx_fp_material_scenario ON fp_materialaufwand(scenario_id)`,
`CREATE INDEX IF NOT EXISTS idx_fp_personal_scenario ON fp_personalkosten(scenario_id)`,
`CREATE INDEX IF NOT EXISTS idx_fp_betrieb_scenario ON fp_betriebliche_aufwendungen(scenario_id)`,
`CREATE INDEX IF NOT EXISTS idx_fp_invest_scenario ON fp_investitionen(scenario_id)`,
`CREATE INDEX IF NOT EXISTS idx_fp_sonst_scenario ON fp_sonst_ertraege(scenario_id)`,
`CREATE INDEX IF NOT EXISTS idx_fp_liquid_scenario ON fp_liquiditaet(scenario_id)`,
`CREATE INDEX IF NOT EXISTS idx_fp_guv_scenario ON fp_guv(scenario_id)`,
`CREATE INDEX IF NOT EXISTS idx_fp_overrides_lookup ON fp_cell_overrides(scenario_id, sheet_name, row_id)`,
]
for (const sql of statements) {
try {
await pool.query(sql)
const label = sql.substring(0, 60).replace(/\s+/g, ' ')
results.push(`OK: ${label}...`)
} catch (err) {
const msg = err instanceof Error ? err.message : String(err)
results.push(`ERROR: ${msg}`)
}
}
return NextResponse.json({ success: true, results })
}
@@ -0,0 +1,73 @@
import { NextRequest, NextResponse } from 'next/server'
import pool from '@/lib/db'
import { generateToken, getClientIp, logAudit } from '@/lib/auth'
import { sendMagicLinkEmail } from '@/lib/email'
import { checkRateLimit, RATE_LIMITS } from '@/lib/rate-limit'
// Generic response returned regardless of whether an investor exists, to
// prevent email enumeration. The client always sees the same success message.
const GENERIC_RESPONSE = {
success: true,
message: 'If this email was invited, a fresh access link has been sent.',
}
export async function POST(request: NextRequest) {
const ip = getClientIp(request) || 'unknown'
// IP-based rate limit to prevent enumeration / abuse
const ipRl = checkRateLimit(`request-link-ip:${ip}`, RATE_LIMITS.authVerify)
if (!ipRl.allowed) {
return NextResponse.json({ error: 'Too many attempts. Try again later.' }, { status: 429 })
}
const body = await request.json().catch(() => ({}))
const { email } = body
if (!email || typeof email !== 'string') {
return NextResponse.json({ error: 'Email required' }, { status: 400 })
}
const normalizedEmail = email.toLowerCase().trim()
// Per-email rate limit (silent — same generic response on throttle so callers
// can't distinguish a throttled-but-valid email from an unknown one)
const emailRl = checkRateLimit(`magic-link:${normalizedEmail}`, RATE_LIMITS.magicLink)
if (!emailRl.allowed) {
await logAudit(null, 'request_link_throttled', { email: normalizedEmail }, request)
return NextResponse.json(GENERIC_RESPONSE)
}
const { rows } = await pool.query(
`SELECT id, email, name, status FROM pitch_investors WHERE email = $1`,
[normalizedEmail],
)
if (rows.length === 0) {
await logAudit(null, 'request_link_unknown_email', { email: normalizedEmail }, request)
return NextResponse.json(GENERIC_RESPONSE)
}
const investor = rows[0]
if (investor.status === 'revoked') {
await logAudit(investor.id, 'request_link_revoked', { email: normalizedEmail }, request)
return NextResponse.json(GENERIC_RESPONSE)
}
const token = generateToken()
const ttlHours = parseInt(process.env.MAGIC_LINK_TTL_HOURS || '72')
const expiresAt = new Date(Date.now() + ttlHours * 60 * 60 * 1000)
await pool.query(
`INSERT INTO pitch_magic_links (investor_id, token, expires_at) VALUES ($1, $2, $3)`,
[investor.id, token, expiresAt],
)
const baseUrl = process.env.PITCH_BASE_URL || 'https://pitch.breakpilot.ai'
const magicLinkUrl = `${baseUrl}/auth/verify?token=${token}`
await sendMagicLinkEmail(investor.email, investor.name, magicLinkUrl)
await logAudit(investor.id, 'request_link_sent', { email: normalizedEmail, expires_at: expiresAt.toISOString() }, request)
return NextResponse.json(GENERIC_RESPONSE)
}
+17 -9
View File
@@ -12,11 +12,12 @@ const SLIDE_DISPLAY_NAMES: Record<string, { de: string; en: string }> = {
'cover': { de: 'Cover', en: 'Cover' },
'problem': { de: 'Das Problem', en: 'The Problem' },
'solution': { de: 'Die Lösung', en: 'The Solution' },
'usp': { de: 'USP', en: 'USP' },
'product': { de: 'Produkte', en: 'Products' },
'how-it-works': { de: 'So funktioniert\'s', en: 'How It Works' },
'market': { de: 'Markt', en: 'Market' },
'business-model': { de: 'Geschäftsmodell', en: 'Business Model' },
'traction': { de: 'Traction', en: 'Traction' },
'traction': { de: 'Meilensteine', en: 'Milestones' },
'competition': { de: 'Wettbewerb', en: 'Competition' },
'team': { de: 'Team', en: 'Team' },
'financials': { de: 'Finanzen', en: 'Financials' },
@@ -47,15 +48,15 @@ Du hast Zugriff auf alle Unternehmensdaten und zitierst immer konkrete Zahlen.
- **Zweisprachig**: Antworte in der Sprache, in der die Frage gestellt wird
## Kernbotschaften (IMMER betonen wenn passend)
1. Kern-Produkt: "BreakPilot COMPLAI — DSGVO-konforme KI-Plattform mit 12 Modulen. Kontinuierliche Code-Security und Compliance-Automatisierung. 110 Gesetze und Regularien, 25.000+ Prüfaspekte."
1. Kern-Produkt: "BreakPilot COMPLAI — DSGVO-konforme KI-Plattform mit 12 Modulen. Kontinuierliche Code-Security und Compliance-Automatisierung. 380+ Gesetze und Regularien, 25.000+ Prüfaspekte."
2. Das Problem: "Unternehmen stehen vor einem strategischen Dilemma: Ohne KI verlieren sie Wettbewerbsfähigkeit. Mit US-KI riskieren sie Datenkontrollverlust. Über 30.000 Unternehmen in DE durch EU-Regulierungen belastet."
3. 12 Module: "Code Security (SAST/DAST/SBOM/Pentesting), CE-Software-Risikobeurteilung, Compliance-Dokumente (VVT/DSFA/TOMs), Audit Manager, DSR/Betroffenenrechte, Consent Management, Notfallpläne, Cookie-Generator, Compliance LLM, Academy, Integration in Kundenprozesse, Sichere Kommunikation."
4. Code & CE: "Kontinuierlich statt einmal im Jahr. CE-Software-Risikobeurteilung auf Code-Basis schon in der Entwicklung. Findings als Tickets mit Implementierungsvorschlägen."
5. EU-Infrastruktur: "BSI-zertifizierte Cloud in Deutschland oder OVH in Frankreich. 100% Datensouveränität. KEINE US-Anbieter. Isolierte Namespaces."
5. EU-Infrastruktur: "BSI-zertifizierte Cloud in Deutschland oder Frankreich. 100% Datensouveränität. KEINE US-Anbieter. Isolierte Namespaces."
6. Zielgruppen: "Maschinen- und Anlagenbauer, Automobilindustrie, Zulieferer und alle produzierenden Unternehmen."
7. Geschäftsmodell: "SaaS, mitarbeiterbasiertes Pricing. Kunden zahlen ~40-50k EUR/Jahr und sparen 50-110k EUR (Pentests 30k, CE-Beurteilungen 20k, Auditmanager 60k+). ROI ab Tag 1."
8. Team: "Skalierung 5→10→17→25→35 MA in 4 Jahren. 37% Engineering, 20% Sales, 9% CS, 9% Compliance/Legal, 9% Marketing. Compliance Consultant als erster Hire — Domain-Expertise vor Engineering."
9. Finanzplan: "Gründung Jul/Aug 2026. 1 Mio. EUR Finanzierung. ~1.200 Kunden und ~10 Mio. ARR bis 2030. Break-Even Mitte 2029. Cash +6,4 Mio. Ende 2030."
7. Geschäftsmodell: "SaaS, mitarbeiterbasiertes Pricing. Drei Tiers: Starter (3.600 EUR/Jahr), Professional (15.000-40.000 EUR/Jahr), Enterprise (ab 50.000 EUR/Jahr). Plus Beratung & Service (10.000-30.000 EUR/Monat). Kunden sparen mehr als sie zahlen — ROI ab Tag 1."
8. Team: "Lean-Team: 2 Gründer + 7 Mitarbeiter bis 2030 (9 Personen gesamt). Erste Einstellung: IT-Recht/Datenschutzjurist (50%). Dann: Security Engineer, Vertrieb, Backend, Kundenbetreuer, Marketing, DevOps. Jede Einstellung an konkreten Umsatzmeilenstein gekoppelt."
9. Finanzplan: "Gründung August 2026. Pre-Seed über Wandeldarlehen (200.000 EUR: 40.000 Investor + 160.000 L-Bank). ~195 Kunden und ~3,3 Mio. Umsatz bis 2030. 9 Mitarbeiter. Optionale 2. Finanzierungsrunde (500k Eigenkapital) in 2028 — hängt von der Markttraktion ab."
## Kommunikationsstil
- Antworte IMMER wie ein Mensch in einem persönlichen Gespräch — ausformulierte Sätze, natürlicher Redefluss
@@ -66,11 +67,18 @@ Du hast Zugriff auf alle Unternehmensdaten und zitierst immer konkrete Zahlen.
- 3-5 Absätze pro Antwort, jeder Absatz ein eigenständiger Gedanke
- Der Text muss sich gut anhören wenn er vorgelesen wird (TTS-optimiert)
## VERSIONS-ISOLATION (ABSOLUT KRITISCH)
- Du kennst NUR die Wandeldarlehen-Version mit 200.000 EUR Finanzierung.
- Es gibt KEINE andere Version. Es gibt KEINE 1-Mio-Version.
- Wenn nach anderen Versionen, anderen Investoren oder anderen Pitch Decks gefragt wird: "Dieses Pitch Deck wurde individuell für Sie erstellt. Es gibt nur diese Version."
- NIEMALS erwähnen: andere Finanzierungssummen, andere Bewertungen, andere Cap Tables.
- Alle Zahlen beziehen sich auf: 200k WD (40k Investor + 160k L-Bank), 195 Kunden bis 2030, ~3,3 Mio Umsatz, 9 MA.
## IP-Schutz-Layer (KRITISCH)
NIEMALS offenbaren: Exakte Modellnamen, Frameworks, Code-Architektur, Datenbankschema, Sicherheitsdetails, Cloud-Provider.
Stattdessen: "Proprietäre KI-Engine", "BSI-zertifizierte EU-Cloud (SysEleven, OVH, Hetzner)", "Isolierte Kunden-Namespaces", "Enterprise-Grade Verschlüsselung".
Stattdessen: "Proprietäre KI-Engine", "BSI-zertifizierte EU-Cloud (SysEleven, Hetzner)", "Isolierte Kunden-Namespaces", "Enterprise-Grade Verschlüsselung".
## Erlaubt: Geschäftsmodell, Preise, Marktdaten, Features, Team, Finanzen, Use of Funds, Hardware-Specs (öffentlich), LLM-Größen (32b/40b/1000b), CE-Risikobeurteilung, Jira-Integration, Meeting-Recorder, Matrix/Jitsi.
## Erlaubt: Geschäftsmodell, Preise, Marktdaten, Features, Team, Finanzen, Use of Funds, Hardware-Specs (öffentlich), LLM-Größen (32b/40b/1000b), CE-Risikobeurteilung, Issue-Tracker-Integration, Meeting-Recorder, Matrix/Jitsi.
## Team-Antworten (WICHTIG)
Wenn nach dem Team gefragt wird: IMMER die Namen, Rollen und Expertise der Gründer aus den bereitgestellten Daten nennen. NIEMALS vage Antworten wie "unser Team vereint Expertise" ohne Namen. Zitiere die konkreten Personen aus den Unternehmensdaten.
@@ -98,7 +106,7 @@ EXAKTES FORMAT (keine Abweichung erlaubt):
KONKRETES BEISPIEL einer vollständigen Antwort:
"Unser AI-First-Ansatz ermöglicht Skalierung ohne lineares Personalwachstum. Der Umsatz steigt von 36k EUR (2026) auf 8.4 Mio EUR (2030), während das Team nur von 2 auf 18 Personen wächst.
"Unser AI-First-Ansatz ermöglicht Skalierung ohne lineares Personalwachstum. Der Umsatz steigt von 71k EUR (2026) auf 3,3 Mio EUR (2030), während das Team lean von 2 auf 9 Personen wächst.
---
[Q] Wie sieht die Kostenstruktur im Detail aus?
+10
View File
@@ -78,6 +78,16 @@ export async function GET() {
}
} catch (error) {
console.error('Database query error:', error)
// Return minimal stub in dev so the pitch renders without a DB connection
if (process.env.NODE_ENV === 'development') {
return NextResponse.json({
company: { name: 'BreakPilot', tagline: '[dev mode — no DB]' },
team: [], financials: [], market: [], competitors: [],
features: [], milestones: [], metrics: [],
funding: { instrument: 'Wandeldarlehen', amount: 500000, valuation_cap: 3000000, currency: 'EUR' },
products: [],
})
}
return NextResponse.json({ error: 'Failed to load pitch data' }, { status: 500 })
}
}
@@ -6,7 +6,7 @@ import { finanzplanToFMResults } from '@/lib/finanzplan/adapter'
export async function POST(request: NextRequest) {
try {
const body = await request.json()
const { scenarioId, source } = body
const { scenarioId, source, force } = body
// If source=finanzplan, use the Finanzplan engine instead
if (source === 'finanzplan') {
@@ -28,6 +28,30 @@ export async function POST(request: NextRequest) {
const client = await pool.connect()
try {
// Fast path: return cached results if they exist (skip when force=true)
const cached = force ? { rows: [] } : await client.query(
'SELECT * FROM pitch_fm_results WHERE scenario_id = $1 ORDER BY month',
[scenarioId]
)
if (cached.rows.length > 0) {
const results = cached.rows
const lastResult = results[results.length - 1]
const breakEvenMonth = results.find(r => r.month > 1 && (r.revenue_eur - r.total_costs_eur) >= 0)?.month || null
return NextResponse.json({
scenario_id: scenarioId,
results,
summary: {
final_arr: lastResult.arr_eur,
final_customers: lastResult.total_customers,
break_even_month: breakEvenMonth,
final_runway: lastResult.runway_months,
final_ltv_cac: lastResult.ltv_cac_ratio,
peak_burn: Math.max(...results.map((r: Record<string, number>) => r.burn_rate_eur)),
total_funding_needed: Math.round(Math.abs(Math.min(...results.map((r: Record<string, number>) => r.cash_balance_eur), 0)) * 100) / 100,
},
})
}
// Load assumptions
const assumptionsRes = await client.query(
'SELECT key, value, value_type FROM pitch_fm_assumptions WHERE scenario_id = $1',
@@ -150,19 +174,15 @@ export async function POST(request: NextRequest) {
})
}
// Save to DB (upsert)
// Save to DB (batch insert — single query instead of 60 individual inserts)
await client.query('DELETE FROM pitch_fm_results WHERE scenario_id = $1', [scenarioId])
for (const r of results) {
await client.query(`
INSERT INTO pitch_fm_results (scenario_id, month, year, month_in_year,
new_customers, churned_customers, total_customers,
mrr_eur, arr_eur, revenue_eur,
cogs_eur, personnel_eur, infra_eur, marketing_eur, total_costs_eur,
employees_count, gross_margin_pct, burn_rate_eur, runway_months,
cac_eur, ltv_eur, ltv_cac_ratio,
cash_balance_eur, cumulative_revenue_eur)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24)
`, [
const cols = 'scenario_id, month, year, month_in_year, new_customers, churned_customers, total_customers, mrr_eur, arr_eur, revenue_eur, cogs_eur, personnel_eur, infra_eur, marketing_eur, total_costs_eur, employees_count, gross_margin_pct, burn_rate_eur, runway_months, cac_eur, ltv_eur, ltv_cac_ratio, cash_balance_eur, cumulative_revenue_eur'
const values: unknown[] = []
const placeholders: string[] = []
results.forEach((r, i) => {
const offset = i * 24
placeholders.push(`(${Array.from({length: 24}, (_, j) => `$${offset + j + 1}`).join(',')})`)
values.push(
scenarioId, r.month, r.year, r.month_in_year,
r.new_customers, r.churned_customers, r.total_customers,
r.mrr_eur, r.arr_eur, r.revenue_eur,
@@ -170,8 +190,9 @@ export async function POST(request: NextRequest) {
r.employees_count, r.gross_margin_pct, r.burn_rate_eur, r.runway_months,
r.cac_eur, r.ltv_eur, r.ltv_cac_ratio,
r.cash_balance_eur, r.cumulative_revenue_eur,
])
}
)
})
await client.query(`INSERT INTO pitch_fm_results (${cols}) VALUES ${placeholders.join(',')}`, values)
const lastResult = results[results.length - 1]
return NextResponse.json({
@@ -39,7 +39,9 @@ export async function GET(
query += ' ORDER BY sort_order'
const { rows } = await pool.query(query, params)
return NextResponse.json({ sheet: sheetName, rows })
return NextResponse.json({ sheet: sheetName, rows }, {
headers: { 'Cache-Control': 'no-store' },
})
} catch (error) {
return NextResponse.json({ error: String(error) }, { status: 500 })
}
+2
View File
@@ -25,6 +25,8 @@ export async function GET() {
sheets,
scenarios: scenarios.rows,
months: { start: '2026-01', end: '2030-12', count: 60, founding: '2026-08' },
}, {
headers: { 'Cache-Control': 'no-store' },
})
} catch (error) {
return NextResponse.json({ error: String(error) }, { status: 500 })
@@ -0,0 +1,17 @@
import { NextResponse } from 'next/server'
import pool from '@/lib/db'
export async function GET() {
try {
const { rows } = await pool.query(
'SELECT key, value, label_de, label_en FROM pitch_pipeline_stats ORDER BY key'
)
const stats: Record<string, { value: number; label_de: string; label_en: string }> = {}
for (const row of rows) {
stats[row.key] = { value: Number(row.value), label_de: row.label_de, label_en: row.label_en }
}
return NextResponse.json(stats)
} catch {
return NextResponse.json({}, { status: 500 })
}
}
+128 -15
View File
@@ -1,6 +1,20 @@
import { NextRequest, NextResponse } from 'next/server'
const TTS_SERVICE_URL = process.env.TTS_SERVICE_URL || 'http://compliance-tts-service:8095'
const LITELLM_URL = process.env.LITELLM_URL || 'https://llm-dev.meghsakha.com'
const LITELLM_API_KEY = process.env.LITELLM_API_KEY || ''
// English via OVH is opt-in (set OVH_TTS_URL_EN). German always uses the
// compliance TTS service (Edge TTS de-DE-ConradNeural → Piper fallback).
const OVH_EN = process.env.OVH_TTS_URL_EN
? {
url: process.env.OVH_TTS_URL_EN,
voice: process.env.OVH_TTS_VOICE_EN || 'English-US.Female-1',
languageCode: 'en-US',
}
: null
const SAMPLE_RATE_HZ = parseInt(process.env.OVH_TTS_SAMPLE_RATE || '16000', 10)
export async function POST(request: NextRequest) {
try {
@@ -11,6 +25,88 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: 'Text is required' }, { status: 400 })
}
if (language === 'en' && OVH_EN) {
return await synthesizeViaOvh(text, OVH_EN)
}
// Fix pronunciation of English words in German TTS
let ttsText = text
if (language === 'de') {
ttsText = ttsText
.replace(/BreakPilot/g, 'Brejk Peilott')
.replace(/Breakpilot/g, 'Brejk Peilott')
.replace(/COMPLAI/g, 'Complei')
.replace(/Executive Summary/g, 'Exekjutiv Sammäri')
.replace(/Onepager/g, 'Wann-Päidscher')
.replace(/Land & Expand/g, 'Länd änd Expänd')
.replace(/Compliance Optimizer/g, 'Compliance Optimeiser')
.replace(/Deep Dive/g, 'Diep Deiv')
.replace(/Use Case/g, 'Juhs Käis')
.replace(/Re-Ranking/g, 'Rie-Ränking')
.replace(/Reranking/g, 'Rie-Ränking')
.replace(/Cross-Encoder/g, 'Kross-Enkoder')
.replace(/Embeddings/g, 'Embäddings')
.replace(/Channel/g, 'Tschännel')
.replace(/Break-Even/g, 'Bräik-Ihwen')
.replace(/Pipeline/g, 'Peiplein')
.replace(/Upside/g, 'Appseid')
.replace(/Cloud/g, 'Klaud')
.replace(/Marketplace/g, 'Marketpläis')
.replace(/Enterprise/g, 'Enterpreis')
.replace(/Startup/g, 'Startapp')
.replace(/Deploy/g, 'Diploi')
.replace(/Scaling/g, 'Skähling')
.replace(/Stack/g, 'Stäck')
.replace(/Tech-Stack/g, 'Teck-Stäck')
}
return await synthesizeViaComplianceService(ttsText, language)
} catch (error) {
console.error('TTS proxy error:', error)
return NextResponse.json({ error: 'TTS service not reachable' }, { status: 503 })
}
}
async function synthesizeViaOvh(
text: string,
cfg: { url: string; voice: string; languageCode: string },
): Promise<NextResponse> {
const res = await fetch(cfg.url, {
method: 'POST',
headers: {
accept: 'application/octet-stream',
'Content-Type': 'application/json',
Authorization: `Bearer ${LITELLM_API_KEY}`,
},
body: JSON.stringify({
encoding: 1, // LINEAR_PCM
language_code: cfg.languageCode,
sample_rate_hz: SAMPLE_RATE_HZ,
text,
voice_name: cfg.voice,
}),
signal: AbortSignal.timeout(30000),
})
if (!res.ok) {
const errorText = await res.text().catch(() => '')
console.error('OVH TTS error:', res.status, errorText.slice(0, 500))
return NextResponse.json({ error: `OVH TTS error (${res.status})` }, { status: 502 })
}
const pcm = Buffer.from(await res.arrayBuffer())
const wav = pcm.subarray(0, 4).toString('ascii') === 'RIFF' ? pcm : wrapPcmAsWav(pcm, SAMPLE_RATE_HZ)
return new NextResponse(new Uint8Array(wav), {
headers: {
'Content-Type': 'audio/wav',
'Cache-Control': 'no-cache',
'X-TTS-Source': 'ovh',
},
})
}
async function synthesizeViaComplianceService(text: string, language: string): Promise<NextResponse> {
const res = await fetch(`${TTS_SERVICE_URL}/synthesize-direct`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -19,28 +115,45 @@ export async function POST(request: NextRequest) {
})
if (!res.ok) {
const errorText = await res.text()
console.error('TTS service error:', res.status, errorText)
return NextResponse.json(
{ error: `TTS service error (${res.status})` },
{ status: 502 }
)
const errorText = await res.text().catch(() => '')
console.error('TTS service error:', res.status, errorText.slice(0, 500))
return NextResponse.json({ error: `TTS service error (${res.status})` }, { status: 502 })
}
const audioBuffer = await res.arrayBuffer()
return new NextResponse(audioBuffer, {
headers: {
'Content-Type': 'audio/mpeg',
'Cache-Control': 'public, max-age=86400', // Cache 24h — texts are static
'Cache-Control': 'no-cache',
'X-TTS-Cache': res.headers.get('X-TTS-Cache') || 'unknown',
'X-TTS-Source': 'compliance',
},
})
} catch (error) {
console.error('TTS proxy error:', error)
return NextResponse.json(
{ error: 'TTS service not reachable' },
{ status: 503 }
)
}
}
// Prepend a minimal 44-byte WAV header to raw 16-bit mono PCM.
// Used only for OVH EN if enabled — OVH Riva returns bare PCM samples.
function wrapPcmAsWav(pcm: Buffer, sampleRateHz: number): Buffer {
const numChannels = 1
const bitsPerSample = 16
const byteRate = (sampleRateHz * numChannels * bitsPerSample) / 8
const blockAlign = (numChannels * bitsPerSample) / 8
const dataSize = pcm.length
const header = Buffer.alloc(44)
header.write('RIFF', 0)
header.writeUInt32LE(36 + dataSize, 4)
header.write('WAVE', 8)
header.write('fmt ', 12)
header.writeUInt32LE(16, 16)
header.writeUInt16LE(1, 20)
header.writeUInt16LE(numChannels, 22)
header.writeUInt32LE(sampleRateHz, 24)
header.writeUInt32LE(byteRate, 28)
header.writeUInt16LE(blockAlign, 32)
header.writeUInt16LE(bitsPerSample, 34)
header.write('data', 36)
header.writeUInt32LE(dataSize, 40)
return Buffer.concat([header, pcm])
}
@@ -13,6 +13,15 @@ export async function GET(request: NextRequest, ctx: Ctx) {
const { versionId } = await ctx.params
// Load version metadata
const ver = await pool.query(
`SELECT name, status FROM pitch_versions WHERE id = $1`,
[versionId],
)
if (ver.rows.length === 0) {
return NextResponse.json({ error: 'Version not found' }, { status: 404 })
}
// Load version data
const { rows } = await pool.query(
`SELECT table_name, data FROM pitch_version_data WHERE version_id = $1`,
@@ -20,7 +29,7 @@ export async function GET(request: NextRequest, ctx: Ctx) {
)
if (rows.length === 0) {
return NextResponse.json({ error: 'Version not found or has no data' }, { status: 404 })
return NextResponse.json({ error: 'Version has no data' }, { status: 404 })
}
const map: Record<string, unknown[]> = {}
@@ -28,7 +37,7 @@ export async function GET(request: NextRequest, ctx: Ctx) {
map[row.table_name] = typeof row.data === 'string' ? JSON.parse(row.data) : row.data
}
// Return PitchData format
// Return PitchData format + version metadata
return NextResponse.json({
company: (map.company || [])[0] || null,
team: map.team || [],
@@ -40,5 +49,8 @@ export async function GET(request: NextRequest, ctx: Ctx) {
metrics: map.metrics || [],
funding: (map.funding || [])[0] || null,
products: map.products || [],
fm_scenarios: map.fm_scenarios || [],
fm_assumptions: map.fm_assumptions || [],
_version: { name: ver.rows[0].name, status: ver.rows[0].status },
})
}
+71 -1
View File
@@ -1,8 +1,43 @@
'use client'
import { motion } from 'framer-motion'
import { useState, FormEvent } from 'react'
type Status = 'idle' | 'submitting' | 'sent' | 'error'
export default function AuthPage() {
const [email, setEmail] = useState('')
const [status, setStatus] = useState<Status>('idle')
const [message, setMessage] = useState<string | null>(null)
async function handleSubmit(e: FormEvent) {
e.preventDefault()
if (!email.trim()) return
setStatus('submitting')
setMessage(null)
try {
const res = await fetch('/api/auth/request-link', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: email.trim() }),
})
const data = await res.json().catch(() => ({}))
if (res.ok) {
setStatus('sent')
setMessage(data.message || 'If this email was invited, a fresh access link has been sent.')
} else {
setStatus('error')
setMessage(data.error || 'Something went wrong. Please try again.')
}
} catch {
setStatus('error')
setMessage('Network error. Please try again.')
}
}
return (
<div className="h-screen flex items-center justify-center bg-[#0a0a1a] relative overflow-hidden">
{/* Background gradient */}
@@ -35,9 +70,44 @@ export default function AuthPage() {
<p className="text-white/50 text-sm leading-relaxed mb-6">
This interactive pitch deck is available by invitation only.
Please check your email for an access link.
If you were invited, enter your email below and we&apos;ll send you a fresh access link.
</p>
{status === 'sent' ? (
<div className="text-left bg-indigo-500/10 border border-indigo-500/20 rounded-lg p-4 mb-5">
<p className="text-indigo-200/90 text-sm leading-relaxed">
{message}
</p>
</div>
) : (
<form onSubmit={handleSubmit} className="text-left mb-5">
<label htmlFor="email" className="block text-white/60 text-xs mb-2 uppercase tracking-wide">
Email address
</label>
<input
id="email"
type="email"
required
autoComplete="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={status === 'submitting'}
placeholder="you@example.com"
className="w-full bg-white/[0.04] border border-white/[0.08] rounded-lg px-4 py-3 text-white/90 text-sm placeholder:text-white/20 focus:outline-none focus:border-indigo-400/50 focus:bg-white/[0.06] transition-colors disabled:opacity-50"
/>
{status === 'error' && message && (
<p className="mt-2 text-rose-300/80 text-xs">{message}</p>
)}
<button
type="submit"
disabled={status === 'submitting' || !email.trim()}
className="w-full mt-4 bg-gradient-to-br from-indigo-500 to-purple-500 hover:from-indigo-400 hover:to-purple-400 disabled:opacity-40 disabled:cursor-not-allowed text-white text-sm font-medium rounded-lg px-4 py-3 transition-all"
>
{status === 'submitting' ? 'Sending…' : 'Send access link'}
</button>
</form>
)}
<div className="border-t border-white/[0.06] pt-5">
<p className="text-white/30 text-xs">
Questions? Contact us at{' '}
+7
View File
@@ -21,6 +21,13 @@ function VerifyContent() {
async function verify() {
try {
// If the investor already has a valid session, skip token verification
const sessionCheck = await fetch('/api/auth/me')
if (sessionCheck.ok) {
router.push('/')
return
}
const res = await fetch('/api/auth/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
+3 -3
View File
@@ -1,10 +1,10 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&display=swap');
@tailwind base;
@tailwind components;
@tailwind utilities;
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&display=swap');
/* === Dark Mode (default) === */
:root {
--bg-primary: #0a0a1a;
@@ -3,7 +3,7 @@
import { useEffect, useState } from 'react'
import { useParams } from 'next/navigation'
import Link from 'next/link'
import { ArrowLeft, Save } from 'lucide-react'
import { ArrowLeft, RefreshCw, Save } from 'lucide-react'
interface Assumption {
id: string
@@ -36,6 +36,7 @@ export default function EditScenarioPage() {
const [loading, setLoading] = useState(true)
const [edits, setEdits] = useState<Record<string, string>>({})
const [savingId, setSavingId] = useState<string | null>(null)
const [recomputing, setRecomputing] = useState(false)
const [toast, setToast] = useState<string | null>(null)
function flashToast(msg: string) {
@@ -56,6 +57,17 @@ export default function EditScenarioPage() {
useEffect(() => { if (scenarioId) load() }, [scenarioId])
async function forceRecompute() {
setRecomputing(true)
const res = await fetch('/api/financial-model/compute', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ scenarioId, force: true }),
})
setRecomputing(false)
flashToast(res.ok ? 'Recomputed successfully' : 'Recompute failed')
}
function setEdit(id: string, val: string) {
setEdits(prev => ({ ...prev, [id]: val }))
}
@@ -108,6 +120,7 @@ export default function EditScenarioPage() {
<ArrowLeft className="w-4 h-4" /> Back to scenarios
</Link>
<div className="flex items-start justify-between gap-4">
<div>
<div className="flex items-center gap-3 mb-1">
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: scenario.color }} />
@@ -120,6 +133,16 @@ export default function EditScenarioPage() {
</div>
{scenario.description && <p className="text-sm text-white/50">{scenario.description}</p>}
</div>
<button
onClick={forceRecompute}
disabled={recomputing}
className="flex items-center gap-2 text-sm px-3 py-1.5 rounded-lg bg-white/[0.06] hover:bg-white/[0.1] text-white/70 hover:text-white disabled:opacity-40 disabled:cursor-wait transition-colors"
title="Clear cache and recompute financial model results"
>
<RefreshCw className={`w-3.5 h-3.5 ${recomputing ? 'animate-spin' : ''}`} />
{recomputing ? 'Computing…' : 'Force Recompute'}
</button>
</div>
<div className="space-y-6">
{Object.entries(byCategory).map(([cat, items]) => (
@@ -1,18 +1,25 @@
'use client'
import { useState } from 'react'
import { useState, useMemo } from 'react'
import { useRouter } from 'next/navigation'
import Link from 'next/link'
import { ArrowLeft } from 'lucide-react'
import { ArrowLeft, Eye, Send } from 'lucide-react'
import { DEFAULT_MESSAGE, DEFAULT_CLOSING, getDefaultGreeting } from '@/lib/email-templates'
export default function NewInvestorPage() {
const router = useRouter()
const [email, setEmail] = useState('')
const [name, setName] = useState('')
const [company, setCompany] = useState('')
const [greeting, setGreeting] = useState('')
const [message, setMessage] = useState(DEFAULT_MESSAGE)
const [closing, setClosing] = useState(DEFAULT_CLOSING)
const [error, setError] = useState('')
const [submitting, setSubmitting] = useState(false)
const effectiveGreeting = greeting || getDefaultGreeting(name || null)
const ttl = process.env.NEXT_PUBLIC_MAGIC_LINK_TTL_HOURS || '72'
async function handleSubmit(e: React.FormEvent) {
e.preventDefault()
setError('')
@@ -21,7 +28,14 @@ export default function NewInvestorPage() {
const res = await fetch('/api/admin/invite', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, name, company }),
body: JSON.stringify({
email,
name,
company,
greeting: effectiveGreeting,
message,
closing,
}),
})
if (res.ok) {
router.push('/pitch-admin/investors')
@@ -37,8 +51,10 @@ export default function NewInvestorPage() {
}
}
const closingHtml = useMemo(() => closing.replace(/\n/g, '<br>'), [closing])
return (
<div className="max-w-xl">
<div className="max-w-5xl">
<Link
href="/pitch-admin/investors"
className="inline-flex items-center gap-2 text-sm text-white/50 hover:text-white/80 mb-6"
@@ -46,15 +62,18 @@ export default function NewInvestorPage() {
<ArrowLeft className="w-4 h-4" /> Back to investors
</Link>
<h1 className="text-2xl font-semibold text-white mb-2">Invite Investor</h1>
<h1 className="text-2xl font-semibold text-white mb-2">Investor einladen</h1>
<p className="text-sm text-white/50 mb-6">
A magic link will be emailed. Single-use, expires in {process.env.NEXT_PUBLIC_MAGIC_LINK_TTL_HOURS || '72'}h.
Der Investor erhaelt eine Email mit einem persoenlichen Magic Link (einmalig, verfaellt nach {ttl}h).
</p>
<div className="grid lg:grid-cols-2 gap-6">
{/* Left: Form */}
<form
onSubmit={handleSubmit}
className="bg-white/[0.04] border border-white/[0.08] rounded-2xl p-6 space-y-4"
className="bg-white/[0.04] border border-white/[0.08] rounded-2xl p-6 space-y-4 self-start"
>
{/* Email */}
<div>
<label htmlFor="email" className="block text-xs font-medium text-white/60 uppercase tracking-wider mb-2">
Email <span className="text-rose-400">*</span>
@@ -66,27 +85,30 @@ export default function NewInvestorPage() {
onChange={(e) => setEmail(e.target.value)}
required
className="w-full bg-black/30 border border-white/10 rounded-lg px-3 py-2.5 text-white focus:outline-none focus:ring-2 focus:ring-indigo-500/40"
placeholder="jane@vc.com"
placeholder="investor@example.com"
/>
</div>
{/* Name */}
<div>
<label htmlFor="name" className="block text-xs font-medium text-white/60 uppercase tracking-wider mb-2">
Name
Name <span className="text-rose-400">*</span>
</label>
<input
id="name"
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
required
className="w-full bg-black/30 border border-white/10 rounded-lg px-3 py-2.5 text-white focus:outline-none focus:ring-2 focus:ring-indigo-500/40"
placeholder="Jane Doe"
placeholder="Dr. Max Mustermann"
/>
</div>
{/* Company (optional) */}
<div>
<label htmlFor="company" className="block text-xs font-medium text-white/60 uppercase tracking-wider mb-2">
Company
Unternehmen <span className="text-white/30 text-[10px] normal-case">(optional)</span>
</label>
<input
id="company"
@@ -94,7 +116,53 @@ export default function NewInvestorPage() {
value={company}
onChange={(e) => setCompany(e.target.value)}
className="w-full bg-black/30 border border-white/10 rounded-lg px-3 py-2.5 text-white focus:outline-none focus:ring-2 focus:ring-indigo-500/40"
placeholder="Acme Ventures"
placeholder="Muster Ventures GmbH"
/>
</div>
<hr className="border-white/[0.06]" />
{/* Greeting */}
<div>
<label htmlFor="greeting" className="block text-xs font-medium text-white/60 uppercase tracking-wider mb-2">
Anrede
</label>
<input
id="greeting"
type="text"
value={greeting}
onChange={(e) => setGreeting(e.target.value)}
className="w-full bg-black/30 border border-white/10 rounded-lg px-3 py-2.5 text-white focus:outline-none focus:ring-2 focus:ring-indigo-500/40"
placeholder={getDefaultGreeting(name || null)}
/>
<p className="text-[10px] text-white/25 mt-1">Leer lassen fuer automatische Anrede basierend auf dem Namen</p>
</div>
{/* Message */}
<div>
<label htmlFor="message" className="block text-xs font-medium text-white/60 uppercase tracking-wider mb-2">
Nachricht
</label>
<textarea
id="message"
value={message}
onChange={(e) => setMessage(e.target.value)}
rows={4}
className="w-full bg-black/30 border border-white/10 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500/40 resize-y"
/>
</div>
{/* Closing */}
<div>
<label htmlFor="closing" className="block text-xs font-medium text-white/60 uppercase tracking-wider mb-2">
Grussformel
</label>
<textarea
id="closing"
value={closing}
onChange={(e) => setClosing(e.target.value)}
rows={3}
className="w-full bg-black/30 border border-white/10 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500/40 resize-y"
/>
</div>
@@ -109,17 +177,88 @@ export default function NewInvestorPage() {
href="/pitch-admin/investors"
className="text-sm text-white/60 hover:text-white px-4 py-2"
>
Cancel
Abbrechen
</Link>
<button
type="submit"
disabled={submitting}
className="bg-gradient-to-r from-indigo-500 to-purple-600 hover:from-indigo-600 hover:to-purple-700 text-white text-sm font-medium px-5 py-2.5 rounded-lg disabled:opacity-50 shadow-lg shadow-indigo-500/20"
className="inline-flex items-center gap-2 bg-gradient-to-r from-indigo-500 to-purple-600 hover:from-indigo-600 hover:to-purple-700 text-white text-sm font-medium px-5 py-2.5 rounded-lg disabled:opacity-50 shadow-lg shadow-indigo-500/20"
>
{submitting ? 'Sending…' : 'Send invite'}
<Send className="w-4 h-4" />
{submitting ? 'Wird gesendet...' : 'Einladung senden'}
</button>
</div>
</form>
{/* Right: Email Preview */}
<div className="self-start">
<div className="flex items-center gap-2 mb-3">
<Eye className="w-4 h-4 text-white/40" />
<h3 className="text-sm font-semibold text-white/60 uppercase tracking-wider">Email-Vorschau</h3>
</div>
<div className="bg-[#0a0a1a] border border-white/[0.08] rounded-2xl p-4 overflow-y-auto max-h-[80vh]">
{/* Email Card */}
<div className="bg-[#111127] border border-indigo-500/20 rounded-xl overflow-hidden">
{/* Header */}
<div className="px-6 pt-6 pb-3">
<p className="text-lg font-semibold text-[#e0e0ff]">BreakPilot ComplAI</p>
<p className="text-xs text-white/40 mt-1">Investor Pitch Deck</p>
</div>
{/* Body */}
<div className="px-6 py-3">
<p className="text-sm text-white/80 mb-3">{effectiveGreeting},</p>
<p className="text-sm text-white/70 leading-relaxed mb-4">{message}</p>
{/* Magic Link Box */}
<div className="bg-indigo-500/[0.08] border border-indigo-500/[0.15] rounded-lg p-3 mb-4">
<p className="text-[10px] font-semibold text-indigo-400/80 uppercase tracking-wider mb-1">
Ihr persoenlicher Zugangslink
</p>
<p className="text-xs text-white/50 leading-relaxed">
Der untenstehende Link ist einmalig und verfaellt nach {ttl} Stunden. Er gewaehrt Ihnen exklusiven Zugang zu unserem interaktiven Pitch Deck inklusive KI-Assistent fuer Ihre Fragen.
</p>
</div>
{/* Button Preview */}
<div className="text-center mb-3">
<span className="inline-block bg-gradient-to-r from-indigo-500 to-purple-600 text-white text-sm font-semibold px-8 py-2.5 rounded-lg">
Pitch Deck oeffnen
</span>
</div>
<p className="text-[10px] text-white/25 mb-4 break-all">
Falls der Button nicht funktioniert: https://pitch.breakpilot.ai/auth/verify?token=...
</p>
{/* Closing */}
<p className="text-sm text-white/70 leading-relaxed" dangerouslySetInnerHTML={{ __html: closingHtml }} />
</div>
{/* Legal Footer */}
<div className="px-6 py-4 border-t border-white/[0.05]">
<p className="text-[9px] font-semibold text-white/30 uppercase tracking-wider mb-1">
Vertraulichkeit &amp; Haftungsausschluss
</p>
<p className="text-[9px] text-white/[0.18] leading-relaxed mb-2">
Dieses Pitch Deck ist vertraulich und wurde ausschliesslich fuer den namentlich eingeladenen Empfaenger erstellt. Durch das Oeffnen des Links erklaert sich der Empfaenger einverstanden: (a) Vertrauliche Behandlung, keine Weitergabe an Dritte. (b) Nutzung ausschliesslich zur Bewertung einer Beteiligung. (c) Vertraulichkeitspflicht fuer 3 Jahre.
</p>
<p className="text-[9px] text-white/[0.18] leading-relaxed mb-3">
Kein Angebot, kein Prospekt. Planzahlen ohne Garantie. Totalverlustrisiko. Deutsches Recht, Gerichtsstand Konstanz.
</p>
<p className="text-[9px] font-semibold text-white/20 uppercase tracking-wider mb-1">
Confidentiality &amp; Disclaimer
</p>
<p className="text-[9px] text-white/[0.13] leading-relaxed">
Confidential. Purpose-limited. 3-year obligation. Not an offer. Projections only. Risk of total loss. German law, Konstanz jurisdiction.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
@@ -8,6 +8,7 @@ import PitchDeck from '@/components/PitchDeck'
export default function PreviewPage() {
const { versionId } = useParams<{ versionId: string }>()
const [data, setData] = useState<PitchData | null>(null)
const [versionMeta, setVersionMeta] = useState<{ name: string; status: string } | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [lang, setLang] = useState<Language>('de')
@@ -24,7 +25,13 @@ export default function PreviewPage() {
if (!r.ok) throw new Error((await r.json().catch(() => ({}))).error || 'Failed to load')
return r.json()
})
.then(setData)
.then(d => {
if (d._version) {
setVersionMeta(d._version)
delete d._version
}
setData(d)
})
.catch(e => setError(e.message))
.finally(() => setLoading(false))
}, [versionId])
@@ -57,8 +64,8 @@ export default function PreviewPage() {
return (
<div className="relative">
{/* Preview banner */}
<div className="fixed top-0 left-0 right-0 z-[100] bg-amber-500/90 text-black text-center py-1.5 text-xs font-semibold">
PREVIEW MODE This is how investors will see this version
<div className="fixed top-0 left-0 right-0 z-[100] bg-amber-500/90 text-black text-center py-1.5 text-xs font-semibold tracking-wide">
PREVIEW: {versionMeta?.name ?? 'Loading...'} {versionMeta?.status === 'draft' ? 'Draft' : 'Committed'}
</div>
<PitchDeck
lang={lang}
+6 -4
View File
@@ -154,10 +154,11 @@ export default function ChatFAB({
ttsAbortRef.current = controller
try {
const textLang = /[äöüÄÖÜß]|(?:^|\s)(?:das|die|der|und|ist|wir|ein|für|mit|auf|von|den|des)\s/i.test(cleanText) ? 'de' : lang
const res = await fetch('/api/presenter/tts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: cleanText, language: lang }),
body: JSON.stringify({ text: cleanText, language: textLang }),
signal: controller.signal,
})
if (!res.ok || controller.signal.aborted) return
@@ -286,10 +287,11 @@ export default function ChatFAB({
}
// If FAQ matched and has a goto_slide, add a GOTO marker to the response
if (faqMatch?.goto_slide) {
const gotoIdx = SLIDE_ORDER.indexOf(faqMatch.goto_slide)
const topMatch = faqMatches[0]
if (topMatch?.goto_slide) {
const gotoIdx = SLIDE_ORDER.indexOf(topMatch.goto_slide)
if (gotoIdx >= 0) {
const suffix = `\n\n[GOTO:${faqMatch.goto_slide}]`
const suffix = `\n\n[GOTO:${topMatch.goto_slide}]`
content += suffix
setMessages(prev => {
const updated = [...prev]
+27 -1
View File
@@ -2,7 +2,7 @@
import { useState, useCallback } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { Menu, X, Maximize, Minimize, Bot } from 'lucide-react'
import { Menu, X, Maximize, Minimize, Bot, Sun, Moon } from 'lucide-react'
import { Language } from '@/lib/types'
import { t } from '@/lib/i18n'
@@ -25,6 +25,15 @@ export default function NavigationFAB({
}: NavigationFABProps) {
const [isOpen, setIsOpen] = useState(false)
const [isFullscreen, setIsFullscreen] = useState(false)
const [isLightMode, setIsLightMode] = useState(false)
const toggleTheme = useCallback(() => {
setIsLightMode(prev => {
const next = !prev
document.documentElement.classList.toggle('theme-light', next)
return next
})
}, [])
const i = t(lang)
const toggleFullscreen = useCallback(() => {
@@ -138,6 +147,23 @@ export default function NavigationFAB({
</div>
</button>
{/* Theme Toggle */}
<button
onClick={toggleTheme}
className="w-full flex items-center justify-between px-3 py-2 rounded-lg
bg-white/[0.05] hover:bg-white/[0.1] transition-colors text-sm"
>
<span className="text-white/50">{lang === 'de' ? 'Modus' : 'Mode'}</span>
<div className="flex items-center gap-1">
<span className={`px-2 py-0.5 rounded text-xs font-medium flex items-center gap-1 ${!isLightMode ? 'bg-indigo-500 text-white' : 'text-white/40'}`}>
<Moon className="w-3 h-3" /> {lang === 'de' ? 'Nacht' : 'Dark'}
</span>
<span className={`px-2 py-0.5 rounded text-xs font-medium flex items-center gap-1 ${isLightMode ? 'bg-amber-500 text-white' : 'text-white/40'}`}>
<Sun className="w-3 h-3" /> {lang === 'de' ? 'Tag' : 'Light'}
</span>
</div>
</button>
{/* Fullscreen */}
<button
onClick={toggleFullscreen}
+60 -8
View File
@@ -1,6 +1,6 @@
'use client'
import { useCallback, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { AnimatePresence } from 'framer-motion'
import { useSlideNavigation } from '@/lib/hooks/useSlideNavigation'
import { useKeyboard } from '@/lib/hooks/useKeyboard'
@@ -29,7 +29,6 @@ import ProductSlide from './slides/ProductSlide'
import HowItWorksSlide from './slides/HowItWorksSlide'
import MarketSlide from './slides/MarketSlide'
import BusinessModelSlide from './slides/BusinessModelSlide'
import TractionSlide from './slides/TractionSlide'
import CompetitionSlide from './slides/CompetitionSlide'
import TeamSlide from './slides/TeamSlide'
import FinancialsSlide from './slides/FinancialsSlide'
@@ -41,6 +40,18 @@ import GTMSlide from './slides/GTMSlide'
import RegulatorySlide from './slides/RegulatorySlide'
import EngineeringSlide from './slides/EngineeringSlide'
import AIPipelineSlide from './slides/AIPipelineSlide'
import USPSlide from './slides/USPSlide'
import DisclaimerSlide from './slides/DisclaimerSlide'
import ExecutiveSummarySlide from './slides/ExecutiveSummarySlide'
import RegulatoryLandscapeSlide from './slides/RegulatoryLandscapeSlide'
import CapTableSlide from './slides/CapTableSlide'
import SavingsSlide from './slides/SavingsSlide'
import SDKDemoSlide from './slides/SDKDemoSlide'
import StrategySlide from './slides/StrategySlide'
import FinanzplanSlide from './slides/FinanzplanSlide'
import GlossarySlide from './slides/GlossarySlide'
import RiskSlide from './slides/RiskSlide'
import MilestonesSlide from './slides/MilestonesSlide'
interface PitchDeckProps {
lang: Language
@@ -57,6 +68,23 @@ export default function PitchDeck({ lang, onToggleLanguage, investor, onLogout,
const error = previewData ? null : fetched.error
const nav = useSlideNavigation()
const [fabOpen, setFabOpen] = useState(false)
const isWandeldarlehen = (data?.funding?.instrument || '').toLowerCase() === 'wandeldarlehen'
// For version previews: use the version's default FM scenario instead of base table default
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const fmScenarios = (previewData as any)?.fm_scenarios as Array<{ id: string; is_default?: boolean }> | undefined
const preferredScenarioId = fmScenarios?.[0]?.is_default
? fmScenarios[0].id
: fmScenarios?.length === 1
? fmScenarios[0].id
: null
// Skip cap-table slide for Wandeldarlehen versions
useEffect(() => {
if (nav.currentSlide === 'cap-table' && isWandeldarlehen) {
nav.nextSlide()
}
}, [nav.currentSlide, isWandeldarlehen, nav.nextSlide])
const presenter = usePresenterMode({
goToSlide: nav.goToSlide,
@@ -132,12 +160,18 @@ export default function PitchDeck({ lang, onToggleLanguage, investor, onLogout,
isPresenting={presenter.state !== 'idle'}
/>
)
case 'executive-summary':
return <ExecutiveSummarySlide lang={lang} data={data} investorId={investor?.id || null} preferredScenarioId={preferredScenarioId} isWandeldarlehen={isWandeldarlehen} />
case 'cover':
return <CoverSlide lang={lang} onNext={nav.nextSlide} funding={data.funding} />
case 'problem':
return <ProblemSlide lang={lang} />
case 'solution':
return <SolutionSlide lang={lang} />
case 'usp':
return <USPSlide lang={lang} />
case 'regulatory-landscape':
return <RegulatoryLandscapeSlide lang={lang} />
case 'product':
return <ProductSlide lang={lang} products={data.products} />
case 'how-it-works':
@@ -145,31 +179,48 @@ export default function PitchDeck({ lang, onToggleLanguage, investor, onLogout,
case 'market':
return <MarketSlide lang={lang} market={data.market} />
case 'business-model':
return <BusinessModelSlide lang={lang} products={data.products} />
return <BusinessModelSlide lang={lang} products={data.products} investorId={investor?.id || null} preferredScenarioId={preferredScenarioId} isWandeldarlehen={isWandeldarlehen} />
case 'traction':
return <TractionSlide lang={lang} milestones={data.milestones} metrics={data.metrics} />
return <MilestonesSlide lang={lang} />
case 'competition':
return <CompetitionSlide lang={lang} features={data.features} competitors={data.competitors} />
case 'team':
return <TeamSlide lang={lang} team={data.team} />
case 'financials':
return <FinancialsSlide lang={lang} investorId={investor?.id || null} />
return <FinancialsSlide lang={lang} investorId={investor?.id || null} preferredScenarioId={preferredScenarioId} isWandeldarlehen={isWandeldarlehen} />
case 'the-ask':
return <TheAskSlide lang={lang} funding={data.funding} />
return <TheAskSlide lang={lang} funding={data.funding} isWandeldarlehen={isWandeldarlehen} />
case 'cap-table':
if (isWandeldarlehen) return null
return <CapTableSlide lang={lang} />
case 'customer-savings':
return <SavingsSlide lang={lang} />
case 'ai-qa':
return <AIQASlide lang={lang} />
case 'annex-assumptions':
return <AssumptionsSlide lang={lang} />
return <AssumptionsSlide lang={lang} investorId={investor?.id || null} preferredScenarioId={preferredScenarioId} isWandeldarlehen={isWandeldarlehen} />
case 'annex-architecture':
return <ArchitectureSlide lang={lang} />
case 'annex-gtm':
return <GTMSlide lang={lang} />
return <GTMSlide lang={lang} isWandeldarlehen={isWandeldarlehen} />
case 'annex-regulatory':
return <RegulatorySlide lang={lang} />
case 'annex-engineering':
return <EngineeringSlide lang={lang} />
case 'annex-aipipeline':
return <AIPipelineSlide lang={lang} />
case 'annex-sdk-demo':
return <SDKDemoSlide lang={lang} />
case 'annex-strategy':
return <StrategySlide lang={lang} isWandeldarlehen={isWandeldarlehen} />
case 'annex-finanzplan':
return <FinanzplanSlide lang={lang} investorId={investor?.id || null} preferredScenarioId={preferredScenarioId} isWandeldarlehen={isWandeldarlehen} />
case 'annex-glossary':
return <GlossarySlide lang={lang} />
case 'risks':
return <RiskSlide lang={lang} />
case 'legal-disclaimer':
return <DisclaimerSlide lang={lang} />
default:
return null
}
@@ -231,6 +282,7 @@ export default function PitchDeck({ lang, onToggleLanguage, investor, onLogout,
onResume={presenter.resume}
onStop={presenter.stop}
onSkip={presenter.skipSlide}
onPrev={presenter.prevSlide}
/>
<AnimatePresence>
@@ -92,6 +92,11 @@ export default function AdminShell({ admin, children }: AdminShellProps) {
<div className="px-3 py-2 mb-2">
<div className="text-sm font-medium text-white/90 truncate">{admin.name}</div>
<div className="text-xs text-white/40 truncate">{admin.email}</div>
<div className="mt-1.5 flex items-center gap-1.5">
<span className="text-[9px] font-mono bg-white/[0.06] text-white/30 px-1.5 py-0.5 rounded">
{process.env.NEXT_PUBLIC_GIT_SHA ?? 'dev'}
</span>
</div>
</div>
<button
onClick={logout}
+123 -66
View File
@@ -1,6 +1,6 @@
'use client'
import { useState } from 'react'
import { useState, useEffect } from 'react'
import { Language } from '@/lib/types'
import { t } from '@/lib/i18n'
import GradientText from '../ui/GradientText'
@@ -18,11 +18,14 @@ import {
Activity,
Shield,
Cpu,
MessageSquare,
Eye,
Gauge,
Network,
Sparkles,
Scale,
BookOpen,
Gavel,
Globe,
} from 'lucide-react'
interface AIPipelineSlideProps {
@@ -31,16 +34,29 @@ interface AIPipelineSlideProps {
type PipelineTab = 'rag' | 'agents' | 'quality'
type PipelineStat = { value: number; label_de: string; label_en: string }
export default function AIPipelineSlide({ lang }: AIPipelineSlideProps) {
const i = t(lang)
const de = lang === 'de'
const [activeTab, setActiveTab] = useState<PipelineTab>('rag')
const [stats, setStats] = useState<Record<string, PipelineStat>>({})
useEffect(() => {
fetch('/api/pipeline-stats', { cache: 'no-store' })
.then(r => r.json())
.then(setStats)
.catch(() => {})
}, [])
const s = (key: string) => stats[key]?.value || 0
const fmtK = (v: number) => v >= 1000 ? `${Math.round(v / 1000)}k+` : `${v}+`
const heroStats = [
{ value: '38+', label: de ? 'Indexierte Verordnungen' : 'Indexed Regulations', sub: 'DSGVO · AI Act · NIS2 · CRA · BDSG · DSA · ...', color: 'text-indigo-400' },
{ value: '6.259', label: de ? 'Extrahierte Controls' : 'Extracted Controls', sub: de ? '79% Source-Match · 9 Verordnungen' : '79% source match · 9 regulations', color: 'text-purple-400' },
{ value: '6', label: de ? 'Qdrant Collections' : 'Qdrant Collections', sub: de ? 'Legal Corpus · DSFA · Recht · Templates · ...' : 'Legal Corpus · DSFA · Law · Templates · ...', color: 'text-emerald-400' },
{ value: '325+', label: de ? 'Abgeleitete Pflichten' : 'Derived Obligations', sub: de ? 'NIS2 · DSGVO · AI Act · CRA · ...' : 'NIS2 · GDPR · AI Act · CRA · ...', color: 'text-amber-400' },
{ value: fmtK(s('legal_sources')), label: de ? 'Rechtsquellen' : 'Legal Sources', sub: de ? 'EU-Verordnungen · DACH-Gesetze · Frameworks' : 'EU regulations · DACH laws · Frameworks', color: 'text-indigo-400' },
{ value: fmtK(s('unique_controls')), label: de ? 'Unique Controls' : 'Unique Controls', sub: de ? 'Prüfbare Compliance-Anforderungen' : 'Auditable compliance requirements', color: 'text-purple-400' },
{ value: fmtK(s('extracted_obligations')), label: de ? 'Extrahierte Pflichten' : 'Extracted Obligations', sub: de ? 'Aus Gesetzestexten abgeleitet' : 'Derived from legal texts', color: 'text-emerald-400' },
{ value: String(s('pipeline_versions') || 6), label: de ? 'Pipeline-Versionen' : 'Pipeline Versions', sub: de ? 'Kontinuierliche Verbesserung' : 'Continuous improvement', color: 'text-amber-400' },
]
const tabs: { id: PipelineTab; label: string; icon: typeof Brain }[] = [
@@ -49,59 +65,105 @@ export default function AIPipelineSlide({ lang }: AIPipelineSlideProps) {
{ id: 'quality', label: de ? 'QA & Infrastruktur' : 'QA & Infrastructure', icon: Gauge },
]
// RAG Pipeline content
// Source categories for investors
const sourceCategories = [
{
icon: Globe,
color: 'text-blue-400',
bg: 'bg-blue-500/10 border-blue-500/20',
title: de ? `EU-Verordnungen (~${s('eu_regulations') || 45})` : `EU Regulations (~${s('eu_regulations') || 45})`,
why: de
? 'Bindende Vorgaben für alle EU-Unternehmen — Verstöße führen zu Bußgeldern bis 4% des Jahresumsatzes.'
: 'Binding requirements for all EU companies — violations lead to fines up to 4% of annual revenue.',
examples: 'DSGVO · AI Act · NIS2 · CRA · MiCA · DSA · Maschinenverordnung · Batterieverordnung',
},
{
icon: Scale,
color: 'text-purple-400',
bg: 'bg-purple-500/10 border-purple-500/20',
title: de ? `DACH-Gesetze (~${s('dach_laws') || 85})` : `DACH Laws (~${s('dach_laws') || 85})`,
why: de
? 'Nationale Umsetzungen und eigenständige Gesetze — oft strenger als EU-Mindeststandards.'
: 'National implementations and standalone laws — often stricter than EU minimum standards.',
examples: 'BDSG · TKG · GwG · HGB · BGB · UrhG · GewO · KRITIS-DachG · AT ABGB · AT KSchG',
},
{
icon: BookOpen,
color: 'text-emerald-400',
bg: 'bg-emerald-500/10 border-emerald-500/20',
title: de ? 'Frameworks & Standards (~15)' : 'Frameworks & Standards (~15)',
why: de
? 'Branchenstandards definieren den Stand der Technik — Aufsichtsbehoerden erwarten deren Einhaltung.'
: 'Industry standards define state of the art — regulators expect compliance with them.',
examples: 'NIST 800-53 · OWASP ASVS · OWASP SAMM · ENISA ICS · NIST Zero Trust · CISA Secure by Design',
},
{
icon: Gavel,
color: 'text-amber-400',
bg: 'bg-amber-500/10 border-amber-500/20',
title: de ? 'DSFA-Leitlinien & Urteile' : 'DPIA Guidelines & Rulings',
why: de
? 'Urteile zeigen wie Gerichte Gesetze auslegen — entscheidend für präzise Compliance-Beratung statt generischer Antworten.'
: 'Court rulings show how laws are interpreted — critical for precise compliance advice instead of generic answers.',
examples: de
? '16 Bundesländer DSFA-Leitlinien · BAG-Urteile · Datenschutzkonferenz-Beschlüsse'
: '16 federal state DPIA guidelines · Labor court rulings · Data protection conference decisions',
},
]
// RAG Pipeline steps
const ragPipelineSteps = [
{
icon: FileText,
color: 'text-blue-400',
bg: 'bg-blue-500/10 border-blue-500/20',
title: de ? '1. Ingestion & QA' : '1. Ingestion & QA',
title: de ? '1. Dokument-Ingestion' : '1. Document Ingestion',
items: de
? ['110+ Verordnungen und Gesetze (EU + DACH)', 'Strukturelles Chunking an Artikel/Absatz-Grenzen', '25.000+ extrahierte Prüfaspekte', 'Deduplizierung + Cross-Regulation Harmonisierung']
: ['110+ laws and regulations (EU + DACH)', 'Structural chunking at article/paragraph boundaries', '25,000+ extracted audit aspects', 'Deduplication + cross-regulation harmonization'],
? [`${s('legal_sources') || 380}+ Rechtsquellen aus EU, Deutschland und Österreich`, 'Strukturelles Chunking an Artikel- und Absatz-Grenzen', 'Automatische Lizenz-Klassifikation (frei / Zitat / geschützt)', 'Geschützte Quellen werden vollständig reformuliert']
: [`${s('legal_sources') || 380}+ legal sources from EU, Germany and Austria`, 'Structural chunking at article and paragraph boundaries', 'Automatic license classification (free / citation / restricted)', 'Protected standards (ISO, BSI) are fully reformulated'],
},
{
icon: Cpu,
color: 'text-purple-400',
bg: 'bg-purple-500/10 border-purple-500/20',
title: de ? '2. Embedding & LLM' : '2. Embedding & LLM',
title: de ? '2. Control-Extraktion' : '2. Control Extraction',
items: de
? ['BGE-M3 Multilingual (1024-dim, lokal)', '120B LLM auf OVH (via LiteLLM, OpenAI-kompatibel)', '1000B LLM auf SysEleven (BSI-zertifiziert)', 'CrossEncoder Re-Ranking + HyDE']
: ['BGE-M3 multilingual (1024-dim, local)', '120B LLM on OVH (via LiteLLM, OpenAI-compatible)', '1000B LLM on SysEleven (BSI-certified)', 'CrossEncoder re-ranking + HyDE'],
? ['LLM extrahiert Pflichten und Anforderungen aus jedem Textabschnitt', `${s('pipeline_versions') || 6} Pipeline-Versionen mit kontinuierlicher Qualitätsverbesserung`, `Obligation Extraction: ${fmtK(s('extracted_obligations'))} einzelne Pflichten identifiziert`, 'Atomic Control Composition: Pflichten werden zu prüfbaren Controls']
: ['LLM extracts obligations and requirements from each text section', `${s('pipeline_versions') || 6} pipeline versions with continuous quality improvement`, `Obligation extraction: ${fmtK(s('extracted_obligations'))} individual duties identified`, 'Atomic control composition: duties become auditable controls'],
},
{
icon: Database,
color: 'text-emerald-400',
bg: 'bg-emerald-500/10 border-emerald-500/20',
title: de ? '3. Vektorspeicher' : '3. Vector Store',
title: de ? '3. Deduplizierung & Speicherung' : '3. Deduplication & Storage',
items: de
? ['Qdrant Vector DB (Hetzner, API-Key gesichert)', '6 Collections: CE, Recht, Gesetze, Datenschutz, DSFA, Templates', 'MinIO Object Storage (Hetzner, S3-kompatibel, TLS)', '25.000+ Prüfaspekte · 110 Gesetze & Regularien · abgeleitete Pflichten']
: ['Qdrant Vector DB (Hetzner, API-key secured)', '6 Collections: CE, Law, Statutes, Privacy, DSFA, Templates', 'MinIO object storage (Hetzner, S3-compatible, TLS)', '25,000+ audit aspects · 110 laws & regulations · derived obligations'],
? [`${fmtK(s('generated_controls'))} generierte Controls → ${fmtK(s('unique_controls'))} nach Deduplizierung`, 'Embedding-basierte Ähnlichkeitserkennung (Cosine Similarity)', 'Cross-Regulation Harmonisierung: gleiche Pflicht aus verschiedenen Gesetzen wird zusammengeführt', `Aktuell: ${fmtK(s('unique_controls'))} atomare Master Controls`]
: [`${fmtK(s('generated_controls'))} generated controls → ${fmtK(s('unique_controls'))} after deduplication`, 'Embedding-based similarity detection (cosine similarity)', 'Cross-regulation harmonization: same obligation from different laws is merged', `Current: ${fmtK(s('unique_controls'))} atomic master controls`],
},
{
icon: Search,
color: 'text-indigo-400',
bg: 'bg-indigo-500/10 border-indigo-500/20',
title: de ? '4. Hybrid Search' : '4. Hybrid Search',
title: de ? '4. Hybrid Search & Beratung' : '4. Hybrid Search & Advisory',
items: de
? ['Multi-Collection-Suche mit Whitelist-Validierung', 'Deutsche Komposita-Zerlegung', 'Cross-Encoder Re-Ranking der Top-K Ergebnisse', 'Quellen-Attribution mit Artikel/Absatz-Referenz']
: ['Multi-collection search with whitelist validation', 'German compound word decomposition', 'Cross-encoder re-ranking of top-K results', 'Source attribution with article/paragraph reference'],
? ['Vektorsuche + Keyword-Suche über alle Rechtsquellen gleichzeitig', 'Cross-Encoder Re-Ranking für präzise Relevanz-Sortierung', 'Quellen-Attribution: Jede Antwort verweist auf Artikel und Absatz', 'Der Compliance-Agent antwortet mit Rechtsgrundlage — nicht mit Vermutungen']
: ['Vector search + keyword search across all legal sources simultaneously', 'Cross-encoder re-ranking for precise relevance sorting', 'Source attribution: Every answer references article and paragraph', 'The compliance agent answers with legal basis — not guesswork'],
},
]
// Multi-Agent System content — UCCA + Policy Engine
const agents = [
{ name: 'UCCA', soul: de ? 'Use-Case Compliance' : 'Use-Case Compliance', desc: de ? 'Policy Engine (45 Regeln) + Eskalation E0E3' : 'Policy engine (45 rules) + escalation E0E3', color: 'text-indigo-400' },
{ name: de ? 'Pflichten-Engine' : 'Obligations Engine', soul: de ? 'abgeleitete Pflichten' : 'derived obligations', desc: de ? 'Multi-Regulation: NIS2, DSGVO, AI Act, CRA, ...' : 'Multi-regulation: NIS2, GDPR, AI Act, CRA, ...', color: 'text-emerald-400' },
{ name: de ? 'Compliance-Berater' : 'Compliance Advisor', soul: de ? 'Legal RAG + LLM' : 'Legal RAG + LLM', desc: de ? 'Wizard-basierter Chatbot mit Qdrant-Kontext' : 'Wizard-based chatbot with Qdrant context', color: 'text-purple-400' },
{ name: de ? 'Dokument-Generator' : 'Document Generator', soul: de ? '20 Templates' : '20 templates', desc: de ? 'AGB, DSE, AV-Vertrag, Widerruf + 16 weitere' : 'T&C, Privacy Policy, DPA, Withdrawal + 16 more', color: 'text-amber-400' },
{ name: de ? 'DSFA-Agent' : 'DSFA Agent', soul: de ? 'Art. 35 DSGVO' : 'Art. 35 GDPR', desc: de ? 'Risikobewertung mit Legal Context Injection' : 'Risk assessment with legal context injection', color: 'text-red-400' },
{ name: de ? 'Schulungs-Engine' : 'Training Engine', soul: de ? 'Academy + TTS' : 'Academy + TTS', desc: de ? '28 Module · Piper TTS · Automatische Videos' : '28 modules · Piper TTS · Automatic videos', color: 'text-blue-400' },
{ name: de ? 'Pflichten-Engine' : 'Obligations Engine', soul: `${fmtK(s('extracted_obligations'))} ${de ? 'Pflichten' : 'obligations'}`, desc: de ? 'Multi-Regulation: NIS2, DSGVO, AI Act, CRA, ...' : 'Multi-regulation: NIS2, GDPR, AI Act, CRA, ...', color: 'text-emerald-400' },
{ name: de ? 'Compliance-Berater' : 'Compliance Advisor', soul: de ? 'Legal RAG + LLM' : 'Legal RAG + LLM', desc: de ? 'Chatbot mit 75+ Rechtsquellen als Wissenbasis' : 'Chatbot with 75+ legal sources as knowledge base', color: 'text-purple-400' },
{ name: de ? 'Dokument-Generator' : 'Document Generator', soul: de ? '7+ Templates' : '7+ templates', desc: de ? 'AGB, DSE, AV-Vertrag, DSFA, FRIA, BV + weitere' : 'T&C, Privacy Policy, DPA, DPIA, FRIA, Works Agreement + more', color: 'text-amber-400' },
{ name: de ? 'DSFA-Agent' : 'DPIA Agent', soul: de ? 'Art. 35 DSGVO' : 'Art. 35 GDPR', desc: de ? 'Risikobewertung mit 16 Bundesländer-Leitlinien' : 'Risk assessment with 16 federal state guidelines', color: 'text-red-400' },
{ name: de ? 'Control-Pipeline' : 'Control Pipeline', soul: de ? '70.000+ Controls' : '70,000+ controls', desc: de ? 'Automatische Extraktion aus neuen Rechtsquellen' : 'Automatic extraction from new legal sources', color: 'text-blue-400' },
]
const agentInfra = [
{ icon: Shield, label: de ? 'Policy Engine' : 'Policy Engine', desc: de ? 'Deterministisch · LLM ist NICHT Wahrheitsquelle' : 'Deterministic · LLM is NOT source of truth' },
{ icon: Brain, label: de ? 'LLM-Schicht' : 'LLM Layer', desc: de ? '120B (OVH) + 1000B (SysEleven BSI) · EU-only' : '120B (OVH) + 1000B (SysEleven BSI) · EU-only' },
{ icon: Brain, label: de ? 'LLM-Schicht' : 'LLM Layer', desc: de ? 'Claude + lokale Modelle · EU-only Hosting' : 'Claude + local models · EU-only hosting' },
{ icon: Network, label: 'LiteLLM Gateway', desc: de ? 'OpenAI-kompatibel · Multi-Provider Routing' : 'OpenAI-compatible · Multi-provider routing' },
{ icon: Activity, label: de ? 'Eskalation E0E3' : 'Escalation E0E3', desc: de ? 'Auto-Approve → Team-Lead → DSB → DSB+Legal' : 'Auto-approve → Team lead → DPO → DPO+Legal' },
]
@@ -113,32 +175,32 @@ export default function AIPipelineSlide({ lang }: AIPipelineSlideProps) {
color: 'text-emerald-400',
title: de ? 'Control Quality Pipeline' : 'Control Quality Pipeline',
items: de
? ['6.259 Controls extrahiert (79% Source-Match)', '3.301 Duplikate entfernt (Phase 5 Normalisierung)', '90+ QA-Skripte: Deduplizierung, Match-Validierung', 'Canonical Controls JSON-Schema-Validierung in CI']
: ['6,259 controls extracted (79% source match)', '3,301 duplicates removed (Phase 5 normalization)', '90+ QA scripts: deduplication, match validation', 'Canonical Controls JSON schema validation in CI'],
? ['97.000 Controls generiert, 70.000+ nach Deduplizierung', '6 Pipeline-Versionen mit steigender Extraktionsqualität', 'Automatische Lizenz-Prüfung: geschützte Quellen werden reformuliert', 'Jeder Control hat Quellen-Referenz auf Artikel und Absatz']
: ['97,000 controls generated, 70,000+ after deduplication', '6 pipeline versions with increasing extraction quality', 'Automatic license check: protected standards are reformulated', 'Every control has source reference to article and paragraph'],
},
{
icon: Eye,
color: 'text-indigo-400',
title: de ? 'RAG Quality & Monitoring' : 'RAG Quality & Monitoring',
title: de ? 'Kontinuierliche Erweiterung' : 'Continuous Expansion',
items: de
? ['PDF-QA-Pipeline: 86% Artikel-Extraktion', 'Multi-Collection-Whitelist-Validierung', 'Qdrant-Deduplizierung: 8-Stufen-Bereinigung', 'Fallback-Handling: RAG-Fehler brechen nie Hauptfunktion']
: ['PDF QA pipeline: 86% article extraction', 'Multi-collection whitelist validation', 'Qdrant deduplication: 8-step cleanup', 'Fallback handling: RAG failures never break main function'],
? ['Neue Gesetze werden automatisch ingestiert und verarbeitet', 'Pipeline erkennt Überschneidungen mit bestehenden Controls', 'Cross-Regulation Mapping: gleiche Pflicht aus DSGVO und BDSG wird verknuepft', 'Wachsender Wissensvorsprung gegenüber manueller Compliance-Beratung']
: ['New laws are automatically ingested and processed', 'Pipeline detects overlaps with existing controls', 'Cross-regulation mapping: same obligation from GDPR and BDSG is linked', 'Growing knowledge advantage over manual compliance consulting'],
},
{
icon: Sparkles,
color: 'text-purple-400',
title: de ? 'CI/CD & Testing' : 'CI/CD & Testing',
items: de
? ['Gitea Actions: Lint → Tests → Validierung bei jedem Push', 'Go-Tests (AI SDK) + Python-Tests (Backend + Crawler + Gateway)', 'Coolify Auto-Deploy mit Health-Check-Monitoring', 'arm64 → amd64 Cross-Build fuer Hetzner Production']
: ['Gitea Actions: Lint → Tests → Validation on every push', 'Go tests (AI SDK) + Python tests (Backend + Crawler + Gateway)', 'Coolify auto-deploy with health check monitoring', 'arm64 → amd64 cross-build for Hetzner production'],
? ['Gitea Actions: Lint → Tests → Validierung bei jedem Push', 'Go-Tests (AI SDK) + Python-Tests (Backend + Pipeline)', 'Orca Auto-Deploy mit Health-Check-Monitoring', 'arm64 → amd64 Cross-Build für Hetzner Production']
: ['Gitea Actions: Lint → Tests → Validation on every push', 'Go tests (AI SDK) + Python tests (Backend + Pipeline)', 'Orca auto-deploy with health check monitoring', 'arm64 → amd64 cross-build for Hetzner production'],
},
{
icon: Zap,
color: 'text-amber-400',
title: de ? 'LLM-Infrastruktur' : 'LLM Infrastructure',
title: de ? 'Infrastruktur' : 'Infrastructure',
items: de
? ['120B Modell auf OVH via LiteLLM (OpenAI-kompatibel)', '1000B Modell auf SysEleven (BSI-zertifiziert)', 'Isolierte Namespaces pro Kunde · Keine US-Provider', 'BGE-M3 Embedding lokal · Lazy Model Loading']
: ['120B model on OVH via LiteLLM (OpenAI-compatible)', '1000B model on SysEleven (BSI-certified)', 'Isolated namespaces per customer · No US providers', 'BGE-M3 embedding local · Lazy model loading'],
? ['Qdrant Vektordatenbank für semantische Suche', 'BGE-M3 Multilingual Embedding (lokal gehostet)', 'MinIO Object Storage (S3-kompatibel, TLS-verschlüsselt)', '100% EU-Cloud · Keine US-Provider · BSI-konforme Hosting-Partner']
: ['Qdrant vector database for semantic search', 'BGE-M3 multilingual embedding (locally hosted)', 'MinIO object storage (S3-compatible, TLS-encrypted)', '100% EU cloud · No US providers · BSI-compliant hosting partners'],
},
]
@@ -179,7 +241,7 @@ export default function AIPipelineSlide({ lang }: AIPipelineSlideProps) {
className={`flex items-center gap-2 px-4 py-2 rounded-xl text-sm transition-all
${activeTab === tab.id
? 'bg-indigo-500/20 text-indigo-300 border border-indigo-500/30'
: 'bg-white/[0.04] text-white/40 border border-transparent hover:text-white/60 hover:bg-white/[0.06]'
: 'bg-white/[0.04] text-white/40 border border-transparent hover:text-white/60 hover:bg-white/[0.06] animate-[pulse_3s_ease-in-out_infinite]'
}`}
>
<Icon className="w-4 h-4" />
@@ -194,15 +256,32 @@ export default function AIPipelineSlide({ lang }: AIPipelineSlideProps) {
<FadeInView delay={0.2} key={activeTab}>
{activeTab === 'rag' && (
<div>
{/* Source Categories — Why each matters */}
<div className="grid md:grid-cols-2 gap-2 mb-4">
{sourceCategories.map((cat, idx) => {
const Icon = cat.icon
return (
<div key={idx} className={`border rounded-xl p-2.5 ${cat.bg}`}>
<div className="flex items-center gap-2 mb-1.5">
<Icon className={`w-4 h-4 ${cat.color}`} />
<h3 className="text-xs font-bold text-white">{cat.title}</h3>
</div>
<p className="text-[10px] text-white/50 mb-1.5 leading-relaxed">{cat.why}</p>
<p className="text-[9px] text-white/25 font-mono leading-tight">{cat.examples}</p>
</div>
)
})}
</div>
{/* Pipeline Flow Visualization */}
<div className="flex items-center justify-center gap-1 mb-4 flex-wrap">
<div className="flex items-center justify-center gap-1 flex-wrap">
{[
{ icon: FileText, label: '38+ PDFs' },
{ icon: Layers, label: 'QA + Chunking' },
{ icon: Cpu, label: 'BGE-M3' },
{ icon: Database, label: '6 Collections' },
{ icon: Search, label: 'Hybrid Search' },
{ icon: Brain, label: '120B / 1000B' },
{ icon: FileText, label: de ? '75+ Quellen' : '75+ Sources' },
{ icon: Layers, label: de ? 'Chunking & Lizenz' : 'Chunking & License' },
{ icon: Cpu, label: de ? 'LLM-Extraktion' : 'LLM Extraction' },
{ icon: Database, label: de ? '70k+ Controls' : '70k+ Controls' },
{ icon: Search, label: de ? 'Hybrid Search' : 'Hybrid Search' },
{ icon: Brain, label: de ? 'Beratung' : 'Advisory' },
].map((step, idx, arr) => (
<div key={idx} className="flex items-center gap-1">
<div className="flex items-center gap-1 px-2 py-1 rounded-lg bg-white/[0.05] border border-white/[0.08]">
@@ -213,28 +292,6 @@ export default function AIPipelineSlide({ lang }: AIPipelineSlideProps) {
</div>
))}
</div>
{/* Pipeline Steps */}
<div className="grid md:grid-cols-2 gap-3">
{ragPipelineSteps.map((step, idx) => {
const Icon = step.icon
return (
<div key={idx} className={`border rounded-xl p-3 ${step.bg}`}>
<div className="flex items-center gap-2 mb-2">
<Icon className={`w-4 h-4 ${step.color}`} />
<h3 className="text-xs font-bold text-white">{step.title}</h3>
</div>
<ul className="space-y-1">
{step.items.map((item, iidx) => (
<li key={iidx} className="flex items-start gap-1.5 text-[11px] text-white/50">
<span className={`w-1 h-1 rounded-full mt-1.5 ${step.color} bg-current shrink-0`} />
{item}
</li>
))}
</ul>
</div>
)
})}
</div>
</div>
)}
@@ -291,7 +348,7 @@ export default function AIPipelineSlide({ lang }: AIPipelineSlideProps) {
<div className="mt-3 pt-3 border-t border-white/5">
<p className="text-[10px] text-white/20">
{de
? 'Wahrheit = Regeln + Evidenz · LLM = Uebersetzer + Subsumtions-Helfer · 100% EU-Cloud'
? 'Wahrheit = Regeln + Evidenz · LLM = Übersetzer + Subsumtions-Helfer · 100% EU-Cloud'
: 'Truth = Rules + Evidence · LLM = Translator + Subsumption Helper · 100% EU Cloud'}
</p>
</div>
@@ -1,129 +1,733 @@
'use client'
import { useState, useEffect, useRef, Fragment } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { Language } from '@/lib/types'
import { t } from '@/lib/i18n'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
import GlassCard from '../ui/GlassCard'
import { Server, Cpu, Shield, Database, Globe, Lock, Layers, Workflow } from 'lucide-react'
import {
Brain, Shield, ScanLine, Zap, Cpu,
Layers, Wrench, X, Users, Lock,
Server, BadgeCheck,
} from 'lucide-react'
interface ArchitectureSlideProps {
lang: Language
interface ArchitectureSlideProps { lang: Language }
type NodeId = 'certifai' | 'complai' | 'scanner' | 'litellm' | 'llm' | 'embeddings' | 'tools'
interface NodeDef {
id: NodeId
icon: React.ElementType
title: string
subtitle: string
color: string
tech: string[]
services: { name: string; desc: string }[]
primary?: boolean
tier: 'product' | 'proxy' | 'inference'
}
function getNodes(de: boolean): NodeDef[] {
return [
{
id: 'certifai', icon: Brain,
title: 'CERTifAI',
subtitle: de ? 'GenAI Mandantenportal' : 'GenAI Tenant Portal',
color: '#c084fc', tier: 'product',
tech: ['Rust', 'Dioxus', 'MongoDB', 'Keycloak', 'SearXNG', 'LangGraph'],
services: [
{ name: 'LiteLLM Dashboard', desc: de ? 'Modellverwaltung & Kostentracking' : 'Model mgmt & cost tracking' },
{ name: 'LibreChat + SSO', desc: de ? 'Mandanten-Chat mit Keycloak' : 'Tenant chat with Keycloak' },
{ name: 'LangGraph Agents', desc: de ? 'Agent-Orchestrierung' : 'Agent orchestration' },
{ name: 'MCP Hub', desc: de ? 'Tool-Integration für KI-Clients' : 'Tool integration for AI clients' },
],
},
{
id: 'complai', icon: Shield,
title: 'COMPLAI',
subtitle: de ? 'Compliance & Audit' : 'Compliance & Audit',
color: '#818cf8', tier: 'product',
tech: ['Next.js 15', 'FastAPI', 'Go/Gin', 'PostgreSQL', 'Qdrant', 'Valkey'],
services: [
{ name: de ? 'DSGVO / AI Act / NIS2' : 'GDPR / AI Act / NIS2', desc: de ? '70k+ auditierbare Controls' : '70k+ auditable controls' },
{ name: 'RAG Pipeline', desc: de ? '75+ Rechtsquellen, semantische Suche' : '75+ legal sources, semantic search' },
{ name: 'Control Pipeline', desc: de ? 'Gesetzestextanalyse via LLM' : 'Legal text analysis via LLM' },
{ name: 'MCP Client', desc: de ? 'Echtzeit-Findings vom Scanner' : 'Real-time findings from Scanner' },
],
},
{
id: 'scanner', icon: ScanLine,
title: 'Compliance Scanner',
subtitle: de ? 'Code-Sicherheit' : 'Code Security',
color: '#34d399', tier: 'product',
tech: ['Rust', 'Axum', 'MongoDB', 'Semgrep', 'Gitleaks', 'Syft'],
services: [
{ name: 'SAST / SBOM / CVE', desc: de ? 'Vollautomatische Pipeline' : 'Fully automated pipeline' },
{ name: de ? 'KI-Triage' : 'AI Triage', desc: de ? 'LLM filtert False Positives' : 'LLM filters false positives' },
{ name: de ? 'KI-Pentest' : 'AI Pentest', desc: de ? 'Autonome Angriffsketten' : 'Autonomous attack chains' },
{ name: 'MCP Server', desc: de ? 'Live-Findings für COMPLAI' : 'Live findings for COMPLAI' },
],
},
{
id: 'litellm', icon: Zap,
title: 'LiteLLM Proxy',
subtitle: de ? 'KI-Gateway & Guardrails' : 'AI Gateway & Guardrails',
color: '#fbbf24', tier: 'proxy', primary: true,
tech: ['OpenAI-kompatible API', 'Bearer Auth', 'Rate Limiting', 'PII-Filter', 'Spend Tracking'],
services: [
{ name: de ? 'Token-Budget' : 'Token Budget', desc: de ? 'Pro-Mandant Kontingente & Abrechnung' : 'Per-tenant quotas & billing' },
{ name: 'PII Guardrails', desc: de ? 'Datenschutz-Filter für alle Anfragen' : 'Privacy filter on all requests' },
{ name: de ? 'Web-Suche (anonym)' : 'Web Search (anon)', desc: de ? 'SearXNG-Proxy, kein US-Anbieter' : 'SearXNG proxy, no US providers' },
{ name: de ? 'Namespace-Isolierung' : 'Namespace Isolation', desc: de ? 'Mandantentrennung per API-Key' : 'Tenant isolation per API key' },
{ name: de ? 'Failover-Routing' : 'Failover Routing', desc: de ? 'Automatisches Fallback' : 'Automatic fallback between models' },
],
},
{
id: 'llm', icon: Cpu,
title: de ? 'LLM Inferenz' : 'LLM Inference',
subtitle: de ? 'Lokale Sprachmodelle' : 'Local Language Models',
color: '#60a5fa', tier: 'inference',
tech: ['Qwen3-32B', 'Qwen3-Coder-30B', 'DeepSeek-R1-8B', 'Ollama'],
services: [
{ name: de ? 'Vollständig lokal' : 'Fully local', desc: de ? 'Daten verlassen nie den Server' : 'Data never leaves the server' },
{ name: de ? 'Air-Gap fähig' : 'Air-Gap Capable', desc: de ? 'Kein Internet erforderlich' : 'No internet required' },
{ name: de ? 'GPU-optimiert' : 'GPU-optimized', desc: de ? 'Dedizierte Inferenz-Hardware' : 'Dedicated inference hardware' },
],
},
{
id: 'embeddings', icon: Layers,
title: 'Embeddings',
subtitle: de ? 'Semantische Suche' : 'Semantic Search',
color: '#a78bfa', tier: 'inference',
tech: ['bge-m3', 'Qdrant Vector DB', 'Sentence-Transformers'],
services: [
{ name: 'RAG Pipeline', desc: de ? '75+ Rechtsquellen indexiert' : '75+ legal sources indexed' },
{ name: de ? 'Semantische Suche' : 'Semantic Search', desc: de ? 'Multi-linguale Einbettungen' : 'Multi-lingual embeddings' },
{ name: de ? 'Lokal' : 'Fully local', desc: de ? 'Keine externen APIs' : 'No external APIs' },
],
},
{
id: 'tools', icon: Wrench,
title: de ? 'KI-Tools' : 'AI Tools',
subtitle: de ? 'Web-Suche & MCP' : 'Web Search & MCP',
color: '#2dd4bf', tier: 'inference',
tech: ['SearXNG', 'MCP Protocol', 'Semgrep API', 'Gitleaks API'],
services: [
{ name: 'SearXNG', desc: de ? 'Anonymisierte EU-Websuche' : 'Anonymized EU web search' },
{ name: 'MCP Tools', desc: de ? 'Auditdokumente & Code-Findings' : 'Audit docs & code findings' },
{ name: de ? 'Kein US-Anbieter' : 'No US providers', desc: de ? '100% DSGVO-konform' : '100% GDPR-compliant' },
],
},
]
}
const LAYERS: { id: string; nodeIds: NodeId[]; tint: string; depth: number }[] = [
{ id: 'product', nodeIds: ['certifai', 'complai', 'scanner'], tint: '#a78bfa', depth: 24 },
{ id: 'proxy', nodeIds: ['litellm'], tint: '#fbbf24', depth: 12 },
{ id: 'inference', nodeIds: ['llm', 'embeddings', 'tools'], tint: '#8b5cf6', depth: 0 },
]
const CSS_KF = `
@keyframes v4FlowDown { from { stroke-dashoffset: 0 } to { stroke-dashoffset: -18px } }
@keyframes v4Pulse { 0%,100% { opacity:1;transform:scale(1) } 50% { opacity:.4;transform:scale(1.4) } }
@keyframes v4Caret { 0%,50% { opacity:1 } 51%,100% { opacity:0 } }
@keyframes v4DotFall {
0% { transform: translateY(-5px); opacity: 0; }
12% { opacity: 1; }
88% { opacity: 1; }
100% { transform: translateY(38px); opacity: 0; }
}
`
const MONO: React.CSSProperties = {
fontFamily: '"JetBrains Mono","SF Mono",ui-monospace,monospace',
fontVariantNumeric: 'tabular-nums',
}
// ── Theme detection ───────────────────────────────────────────────────────────
function useIsLight() {
const [isLight, setIsLight] = useState(false)
useEffect(() => {
const check = () => setIsLight(document.documentElement.classList.contains('theme-light'))
check()
const obs = new MutationObserver(check)
obs.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] })
return () => obs.disconnect()
}, [])
return isLight
}
// ── Ticker primitives ─────────────────────────────────────────────────────────
function useTicker(fn: () => void, min = 140, max = 360, skipChance = 0.1) {
const ref = useRef(fn)
ref.current = fn
useEffect(() => {
let tid: ReturnType<typeof setTimeout>
const loop = () => {
if (Math.random() > skipChance) ref.current()
tid = setTimeout(loop, min + Math.random() * (max - min))
}
loop()
return () => clearTimeout(tid)
}, [min, max, skipChance])
}
function TickerShell({ color, children, isLight }: { color: string; children: React.ReactNode; isLight: boolean }) {
return (
<div style={{
...MONO,
marginTop: 7, padding: '5px 9px',
background: isLight ? '#f1f5f9' : 'rgba(0,0,0,.38)',
border: `1px solid ${color}${isLight ? '55' : '55'}`, borderRadius: 6,
fontSize: 10, color: isLight ? '#475569' : 'rgba(236,233,247,.88)',
display: 'flex', alignItems: 'center', gap: 6,
whiteSpace: 'nowrap', overflow: 'hidden', height: 22,
}}>{children}</div>
)
}
function Caret({ color }: { color: string }) {
return (
<span style={{
display: 'inline-block', width: 5, height: 9, marginLeft: -2,
background: color, animation: 'v4Caret 1s step-end infinite',
}} />
)
}
// ── Per-node tickers ──────────────────────────────────────────────────────────
function TickCertifAI({ color, isLight }: { color: string; isLight: boolean }) {
const [n, setN] = useState(8421)
const [hash, setHash] = useState('9f3a…e10b')
const pool = 'abcdef0123456789'
const r = (k: number) => Array.from({ length: k }, () => pool[Math.floor(Math.random() * pool.length)]).join('')
useTicker(() => { setN(v => v + 1); setHash(`${r(4)}${r(4)}`) }, 1000, 2000, 0.1)
return (
<TickerShell color={color} isLight={isLight}>
<span style={{ color: isLight ? '#16a34a' : '#4ade80' }}></span>
<span style={{ color, opacity: .85 }}>sig</span>
<span style={{ color: isLight ? '#1a1a2e' : '#f5f3fc', fontWeight: 600 }}>{n.toLocaleString()}</span>
<span style={{ color: isLight ? '#94a3b8' : 'rgba(236,233,247,.55)' }}>{hash}</span>
</TickerShell>
)
}
function TickComplAI({ color, isLight }: { color: string; isLight: boolean }) {
const [evals, setEvals] = useState(1284)
const [rate, setRate] = useState(99.2)
useTicker(() => {
setEvals(v => v + 1 + Math.floor(Math.random() * 3))
setRate(r => Math.max(97, Math.min(99.9, r + (Math.random() - 0.5) * 0.4)))
}, 200, 500, 0.1)
return (
<TickerShell color={color} isLight={isLight}>
<span style={{ color: isLight ? '#16a34a' : '#4ade80' }}></span>
<span style={{ color, opacity: .85 }}>eval</span>
<span style={{ color: isLight ? '#1a1a2e' : '#f5f3fc', fontWeight: 600 }}>{evals.toLocaleString()}</span>
<span style={{ color: isLight ? '#94a3b8' : 'rgba(236,233,247,.45)' }}>pass</span>
<span style={{ color: isLight ? '#16a34a' : '#4ade80' }}>{rate.toFixed(1)}%</span>
</TickerShell>
)
}
function TickScanner({ color, isLight }: { color: string; isLight: boolean }) {
const lines = [
{ k: 'PASS', c: '#16a34a', cd: '#4ade80', t: 'CWE-79 xss check' },
{ k: 'WARN', c: '#d97706', cd: '#fbbf24', t: 'drift: model v2.1→2.2' },
{ k: 'PASS', c: '#16a34a', cd: '#4ade80', t: 'bias: demographic parity' },
{ k: 'FAIL', c: '#dc2626', cd: '#f87171', t: 'license: GPL-3 detected' },
{ k: 'PASS', c: '#16a34a', cd: '#4ade80', t: 'prompt-inject: 214 vectors' },
{ k: 'SCAN', c: '#7c3aed', cd: '#a78bfa', t: 'artifact model-card.json' },
]
const [i, setI] = useState(0)
useTicker(() => setI(x => (x + 1) % lines.length), 700, 1200, 0.05)
const l = lines[i]
return (
<TickerShell color={color} isLight={isLight}>
<span style={{ color: isLight ? l.c : l.cd, fontWeight: 600, minWidth: 30 }}>{l.k}</span>
<span style={{ color: isLight ? '#334155' : 'rgba(236,233,247,.85)', overflow: 'hidden', textOverflow: 'ellipsis', flex: 1 }}>{l.t}</span>
</TickerShell>
)
}
function TickLiteLLM({ color, isLight }: { color: string; isLight: boolean }) {
const [rps, setRps] = useState(428)
const [p50, setP50] = useState(84)
useTicker(() => {
setRps(v => Math.max(200, Math.min(800, v + (Math.random() - 0.5) * 60)))
setP50(v => Math.max(40, Math.min(160, v + (Math.random() - 0.5) * 20)))
}, 250, 500, 0.05)
return (
<TickerShell color={color} isLight={isLight}>
<span style={{ color: '#d97706' }}></span>
<span style={{ color, opacity: .9 }}>req/s</span>
<span style={{ color: isLight ? '#1a1a2e' : '#f5f3fc', fontWeight: 600 }}>{Math.round(rps)}</span>
<span style={{ color: isLight ? '#94a3b8' : 'rgba(236,233,247,.4)' }}>·</span>
<span style={{ color, opacity: .9 }}>p50</span>
<span style={{ color: isLight ? '#1a1a2e' : '#f5f3fc', fontWeight: 600 }}>{Math.round(p50)}ms</span>
<Caret color={color} />
</TickerShell>
)
}
function TickLLM({ color, isLight }: { color: string; isLight: boolean }) {
const [tokens, setTokens] = useState(14832)
const [stream, setStream] = useState('t_a91f')
const pool = 'abcdef0123456789'
useTicker(() => {
setTokens(v => v + 1 + Math.floor(Math.random() * 5))
setStream('t_' + Array.from({ length: 4 }, () => pool[Math.floor(Math.random() * pool.length)]).join(''))
}, 120, 340, 0.15)
return (
<TickerShell color={color} isLight={isLight}>
<span style={{ color: isLight ? '#16a34a' : '#4ade80' }}></span>
<span style={{ color, opacity: .85 }}>tok</span>
<span style={{ color: isLight ? '#1a1a2e' : '#f5f3fc', fontWeight: 600 }}>{tokens.toLocaleString()}</span>
<span style={{ color: isLight ? '#94a3b8' : 'rgba(236,233,247,.35)' }}></span>
<span style={{ color }}>{stream}</span>
<Caret color={color} />
</TickerShell>
)
}
function TickEmbeddings({ color, isLight }: { color: string; isLight: boolean }) {
const [vecs, setVecs] = useState(284112)
useTicker(() => setVecs(v => v + 1 + Math.floor(Math.random() * 8)), 180, 420, 0.1)
return (
<TickerShell color={color} isLight={isLight}>
<span style={{ color: isLight ? '#16a34a' : '#4ade80' }}></span>
<span style={{ color, opacity: .85 }}>idx</span>
<span style={{ color: isLight ? '#1a1a2e' : '#f5f3fc', fontWeight: 600 }}>{vecs.toLocaleString()}</span>
<span style={{ color: isLight ? '#94a3b8' : 'rgba(236,233,247,.4)' }}>· 1024d</span>
<Caret color={color} />
</TickerShell>
)
}
function TickTools({ color, isLight }: { color: string; isLight: boolean }) {
const ops = [
'search("BSI C5 controls")', 'fetch eur-lex.europa.eu',
'grep -r "DSGVO"', 'read docs/policy.md',
'mcp.call(filesystem)', 'search("vLLM 0.6 release")',
]
const [i, setI] = useState(0)
useTicker(() => setI(x => (x + 1) % ops.length), 900, 1600, 0.05)
return (
<TickerShell color={color} isLight={isLight}>
<span style={{ color: isLight ? '#16a34a' : '#4ade80' }}></span>
<span style={{ color, opacity: .85 }}>call</span>
<span style={{ color: isLight ? '#334155' : '#f5f3fc', overflow: 'hidden', textOverflow: 'ellipsis', flex: 1 }}>{ops[i]}</span>
</TickerShell>
)
}
const NODE_TICKER: Record<NodeId, React.ComponentType<{ color: string; isLight: boolean }>> = {
certifai: TickCertifAI,
complai: TickComplAI,
scanner: TickScanner,
litellm: TickLiteLLM,
llm: TickLLM,
embeddings: TickEmbeddings,
tools: TickTools,
}
// ── Animated connector ────────────────────────────────────────────────────────
function LayerConnector({ tint }: { tint: string }) {
const tracks = [
{ x: '32%', primary: false },
{ x: '50%', primary: true },
{ x: '68%', primary: false },
]
return (
<div style={{ position: 'relative', height: 34, width: '100%', maxWidth: 960, margin: '0 auto' }}>
{tracks.map(({ x, primary }, ti) => {
const color = primary ? '#fbbf24' : tint
const dots = primary ? 4 : 3
const dur = primary ? 1.6 : 2.4
return (
<div key={ti} style={{ position: 'absolute', left: x, top: 0, bottom: 0, transform: 'translateX(-50%)' }}>
<div style={{
position: 'absolute', left: -0.75, top: 0, bottom: 0, width: 1.5,
background: `linear-gradient(180deg, ${color}00, ${color}55 40%, ${color}55 60%, ${color}00)`,
}} />
{Array.from({ length: dots }, (_, j) => (
<div key={j} style={{
position: 'absolute', top: 0, left: -3, width: 6, height: 6, borderRadius: '50%',
background: color, boxShadow: `0 0 7px ${color}`,
animation: `v4DotFall ${dur}s ${-(j / dots) * dur}s linear infinite`,
}} />
))}
</div>
)
})}
</div>
)
}
// ── Node card ─────────────────────────────────────────────────────────────────
function NodeCard({ node, selected, onClick, isLight }: {
node: NodeDef; selected: boolean; onClick: () => void; isLight: boolean
}) {
const [hover, setHover] = useState(false)
const active = hover || selected
const c = node.color
const Ticker = NODE_TICKER[node.id]
const Icon = node.icon
return (
<button
onClick={onClick}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
style={{
flex: 1,
background: active
? `linear-gradient(180deg, ${c}${isLight ? '20' : '33'}, ${c}${isLight ? '0a' : '12'})`
: isLight
? 'linear-gradient(180deg, #ffffff, #f8fafc)'
: 'linear-gradient(180deg, rgba(255,255,255,.055), rgba(255,255,255,.015))',
border: `1px solid ${active ? c : isLight ? 'rgba(0,0,0,.1)' : 'rgba(255,255,255,.14)'}`,
borderRadius: 12, padding: '12px 14px',
cursor: 'pointer', textAlign: 'left',
color: isLight ? '#1a1a2e' : '#ece9f7', fontFamily: 'inherit',
display: 'flex', flexDirection: 'column',
transition: 'all .2s ease',
transform: active ? 'translateY(-1px)' : 'none',
boxShadow: active
? `0 8px 26px ${c}44, 0 0 0 4px ${c}14`
: isLight ? '0 1px 4px rgba(0,0,0,.06)' : '0 1px 0 rgba(255,255,255,.04)',
minWidth: 0, position: 'relative',
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{
width: 36, height: 36, borderRadius: 10, flexShrink: 0,
background: `linear-gradient(135deg, ${c}3a, ${c}10)`,
border: `1px solid ${c}66`,
display: 'flex', alignItems: 'center', justifyContent: 'center',
color: c,
boxShadow: node.primary ? `inset 0 0 14px ${c}40` : 'none',
}}>
<Icon style={{ width: 18, height: 18 }} />
</div>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{
fontSize: 13, fontWeight: 600,
color: isLight ? '#1a1a2e' : '#f7f5fc',
letterSpacing: -0.1, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
}}>{node.title}</div>
<div style={{
fontSize: 10.5,
color: isLight ? '#64748b' : 'rgba(236,233,247,.65)',
marginTop: 1, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
}}>{node.subtitle}</div>
</div>
</div>
<Ticker color={c} isLight={isLight} />
{node.primary && (
<div style={{
position: 'absolute', top: -1, right: -1,
width: 6, height: 6, borderRadius: '50%',
background: '#fbbf24', boxShadow: '0 0 8px #fbbf24',
animation: 'v4Pulse 1.6s ease-in-out infinite',
}} />
)}
</button>
)
}
// ── 3D slab ───────────────────────────────────────────────────────────────────
function LayerSlab({ label, sublabel, nodes, tint, depth, selectedId, onSelect, isLight }: {
label: string; sublabel: string; nodes: NodeDef[]
tint: string; depth: number
selectedId: NodeId | null; onSelect: (id: NodeId) => void
isLight: boolean
}) {
const isProxy = nodes.length === 1 && !!nodes[0].primary
return (
<div style={{
position: 'relative', margin: '0 auto',
padding: '14px 20px 18px', width: '100%', maxWidth: 960,
background: isLight
? `linear-gradient(180deg, ${tint}18 0%, ${tint}08 60%, rgba(248,250,252,.98) 100%)`
: `linear-gradient(180deg, ${tint}26 0%, ${tint}12 60%, rgba(14,8,28,.85) 100%)`,
border: `1px solid ${tint}${isLight ? '44' : '66'}`,
borderRadius: 16,
boxShadow: isLight
? `0 -2px 16px ${tint}18, 0 8px 30px rgba(0,0,0,.05), inset 0 1px 0 ${tint}44`
: `0 -6px 30px ${tint}22, 0 24px 60px rgba(0,0,0,.6), inset 0 1px 0 ${tint}55, inset 0 -1px 0 rgba(0,0,0,.4)`,
transform: `perspective(2000px) rotateX(12deg) translateZ(${depth}px)`,
}}>
<div style={{
position: 'absolute', top: 0, left: 20, right: 20, height: 1,
background: `linear-gradient(90deg, transparent, ${tint}${isLight ? 'aa' : 'cc'}, transparent)`,
pointerEvents: 'none',
}} />
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 }}>
<div style={{
fontSize: 9.5, letterSpacing: 2, textTransform: 'uppercase' as const, fontWeight: 600,
color: tint,
background: isLight ? `${tint}18` : `${tint}20`,
padding: '3px 9px', borderRadius: 99,
border: `1px solid ${tint}${isLight ? '44' : '50'}`, whiteSpace: 'nowrap',
}}>{label}</div>
<div style={{
fontSize: 11,
color: isLight ? '#64748b' : 'rgba(236,233,247,.55)',
whiteSpace: 'nowrap',
}}>{sublabel}</div>
</div>
<div style={{ display: 'flex', gap: 10, justifyContent: isProxy ? 'center' : undefined }}>
{isProxy ? (
<div style={{ width: '42%', minWidth: 260, display: 'flex' }}>
<NodeCard node={nodes[0]} selected={selectedId === nodes[0].id} onClick={() => onSelect(nodes[0].id)} isLight={isLight} />
</div>
) : (
nodes.map(n => (
<NodeCard key={n.id} node={n} selected={selectedId === n.id} onClick={() => onSelect(n.id)} isLight={isLight} />
))
)}
</div>
</div>
)
}
// ── Main slide ────────────────────────────────────────────────────────────────
export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {
const i = t(lang)
const de = lang === 'de'
const isLight = useIsLight()
const allNodes = getNodes(de)
const nodeMap = Object.fromEntries(allNodes.map(n => [n.id, n])) as Record<NodeId, NodeDef>
const layers = [
{
icon: Server,
color: 'text-indigo-400',
bg: 'bg-indigo-500/10 border-indigo-500/20',
title: de ? 'Hardware-Schicht' : 'Hardware Layer',
items: [
{ label: 'ComplAI Mini', desc: 'Mac Mini M4 · 16 GB · Llama 3.2 3B' },
{ label: 'ComplAI Studio', desc: 'Mac Studio M4 Max · 64 GB · Qwen 2.5 32B' },
{ label: 'ComplAI Cloud', desc: de ? 'Managed GPU-Cluster · Multi-Model' : 'Managed GPU Cluster · Multi-Model' },
],
},
{
icon: Cpu,
color: 'text-purple-400',
bg: 'bg-purple-500/10 border-purple-500/20',
title: de ? 'KI-Engine' : 'AI Engine',
items: [
{ label: 'Ollama Runtime', desc: de ? 'Lokale LLM-Inferenz, GPU-optimiert' : 'Local LLM inference, GPU-optimized' },
{ label: 'RAG Pipeline', desc: de ? 'Vektorsuche mit Compliance-Wissensbasis' : 'Vector search with compliance knowledge base' },
{ label: 'Agent Framework', desc: de ? 'Autonome Compliance-Agenten (Audit, Monitoring, Reporting)' : 'Autonomous compliance agents (Audit, Monitoring, Reporting)' },
],
},
{
icon: Shield,
color: 'text-emerald-400',
bg: 'bg-emerald-500/10 border-emerald-500/20',
title: de ? 'Compliance-Module' : 'Compliance Modules',
items: [
{ label: 'DSGVO Engine', desc: de ? 'VVT, DSFA, Betroffenenrechte, Loeschkonzept' : 'RoPA, DPIA, Data Subject Rights, Deletion Concept' },
{ label: 'AI Act Module', desc: de ? 'Risikoklassifizierung, Konformitaetsbewertung, Dokumentation' : 'Risk Classification, Conformity Assessment, Documentation' },
{ label: 'NIS2 Module', desc: de ? 'Cybersecurity-Policies, Incident Response, Meldewege' : 'Cybersecurity Policies, Incident Response, Reporting Chains' },
],
},
{
icon: Layers,
color: 'text-blue-400',
bg: 'bg-blue-500/10 border-blue-500/20',
title: de ? 'Plattform-Services' : 'Platform Services',
items: [
{ label: de ? 'Admin-Dashboard' : 'Admin Dashboard', desc: 'Next.js · ' + (de ? 'Mandantenfaehig · Rollenbasiert' : 'Multi-Tenant · Role-Based') },
{ label: 'SDK API', desc: 'Go/Gin · REST · ' + (de ? 'Tenant-isoliert' : 'Tenant-Isolated') },
{ label: 'DevSecOps Suite', desc: 'Semgrep · Trivy · Gitleaks · CycloneDX SBOM' },
],
},
]
const [activeId, setActiveId] = useState<NodeId | null>(null)
function toggle(id: NodeId) { setActiveId(prev => prev === id ? null : id) }
const active = activeId ? nodeMap[activeId] : null
const securityFeatures = [
{ icon: Lock, label: de ? 'Zero-Trust Architektur' : 'Zero-Trust Architecture' },
{ icon: Database, label: de ? 'Daten verlassen nie das Unternehmen' : 'Data Never Leaves the Company' },
{ icon: Globe, label: de ? 'Kein Cloud-Abhaengigkeit' : 'No Cloud Dependency' },
{ icon: Workflow, label: de ? 'Air-Gap faehig' : 'Air-Gap Capable' },
]
const tenants = de
? ['Mandant A', 'Mandant B', 'Mandant C', 'Mandant N…']
: ['Namespace A', 'Namespace B', 'Namespace C', 'Namespace N…']
const layerLabels = de
? ['01 · Anwendung', '02 · Gateway', '03 · Infrastruktur']
: ['01 · Application', '02 · Gateway', '03 · Infrastructure']
const layerSublabels = de
? ['Benutzeroberflächen', 'Routing & Guardrails', 'Compute & Daten']
: ['User-facing services', 'Routing & guardrails', 'Compute & data']
return (
<div>
<FadeInView className="text-center mb-8">
<p className="text-xs font-mono text-indigo-400/60 uppercase tracking-widest mb-2">
<div className="space-y-3">
<style>{CSS_KF}</style>
<FadeInView className="text-center mb-3">
<p className="text-[10px] font-mono text-indigo-400/50 uppercase tracking-widest mb-1.5">
{de ? 'Anhang' : 'Appendix'}
</p>
<h2 className="text-4xl md:text-5xl font-bold mb-3">
<h2 className="text-3xl md:text-4xl font-bold mb-1.5">
<GradientText>{i.annex.architecture.title}</GradientText>
</h2>
<p className="text-lg text-white/50 max-w-2xl mx-auto">{i.annex.architecture.subtitle}</p>
<p className="text-xs text-white/35">
{de ? 'Klicke auf eine Station für Details' : 'Click any node to explore'}
</p>
</FadeInView>
{/* Architecture Layers */}
<div className="grid md:grid-cols-2 gap-4 mb-6">
{layers.map((layer, idx) => {
const Icon = layer.icon
<FadeInView delay={0.15}>
<div className="flex items-center justify-center gap-2 flex-wrap mb-3 px-[4%]">
<Users className="w-3 h-3 text-white/25 flex-shrink-0" />
<span className="text-[9px] font-mono text-white/25 uppercase tracking-widest mr-1">
{de ? 'Kundenmandanten' : 'Customer Namespaces'}
</span>
{tenants.map(tn => (
<span key={tn} className="text-[9px] px-2 py-0.5 rounded-full border border-white/[0.08] bg-white/[0.03] text-white/35 font-mono">
{tn}
</span>
))}
</div>
{/* ── Main canvas ── */}
<div style={{
position: 'relative',
background: isLight
? 'linear-gradient(180deg, #f0f4ff 0%, #eef2ff 50%, #f0f4ff 100%)'
: 'linear-gradient(180deg, #0a0618 0%, #140a28 50%, #1a0f34 100%)',
borderRadius: 16, overflow: 'hidden',
padding: '22px 16px 20px',
fontFamily: '"Inter", system-ui, -apple-system, sans-serif',
WebkitFontSmoothing: 'antialiased',
MozOsxFontSmoothing: 'grayscale',
} as React.CSSProperties}>
{/* Ambient glows */}
{!isLight && (
<>
<div style={{
position: 'absolute', top: -80, left: '25%',
width: 400, height: 400, borderRadius: '50%',
background: 'radial-gradient(circle, rgba(167,139,250,.2), transparent 65%)',
filter: 'blur(50px)', pointerEvents: 'none',
}} />
<div style={{
position: 'absolute', bottom: -100, right: '15%',
width: 500, height: 500, borderRadius: '50%',
background: 'radial-gradient(circle, rgba(139,92,246,.15), transparent 65%)',
filter: 'blur(50px)', pointerEvents: 'none',
}} />
</>
)}
{/* Slabs + connectors */}
<div style={{
display: 'flex', flexDirection: 'column', alignItems: 'center',
position: 'relative', zIndex: 1,
perspective: '2000px', perspectiveOrigin: '50% 0%',
}}>
{LAYERS.map((layer, li) => {
const nodes = layer.nodeIds.map(id => nodeMap[id])
return (
<FadeInView key={idx} delay={0.2 + idx * 0.1}>
<div className={`border rounded-xl p-4 ${layer.bg}`}>
<div className="flex items-center gap-2 mb-3">
<Icon className={`w-5 h-5 ${layer.color}`} />
<h3 className="text-sm font-bold text-white">{layer.title}</h3>
<Fragment key={layer.id}>
<LayerSlab
label={layerLabels[li]}
sublabel={layerSublabels[li]}
nodes={nodes}
tint={layer.tint}
depth={layer.depth}
selectedId={activeId}
onSelect={toggle}
isLight={isLight}
/>
{li < LAYERS.length - 1 && <LayerConnector tint={layer.tint} />}
</Fragment>
)
})}
</div>
{/* Footer badges */}
<div style={{
display: 'flex', justifyContent: 'center', gap: 8,
flexWrap: 'wrap', marginTop: 20, position: 'relative', zIndex: 1,
}}>
{([
{ Icon: Lock, label: de ? 'Kein US-Anbieter · 100% DSGVO' : 'No US providers · 100% GDPR' },
{ Icon: Server, label: de ? 'BSI-zertifiziertes Rechenzentrum' : 'BSI-certified data center' },
{ Icon: BadgeCheck, label: de ? 'EU-souveräne Inferenz' : 'EU-sovereign inference' },
] as { Icon: React.ElementType; label: string }[]).map(({ Icon, label }) => (
<div key={label} style={{
display: 'flex', alignItems: 'center', gap: 6,
padding: '5px 11px', borderRadius: 99,
background: isLight ? '#ffffff' : 'rgba(10,6,24,.82)',
border: `1px solid ${isLight ? 'rgba(0,0,0,.1)' : 'rgba(167,139,250,.28)'}`,
fontSize: 10.5,
color: isLight ? '#64748b' : 'rgba(236,233,247,.7)',
whiteSpace: 'nowrap',
boxShadow: isLight ? '0 1px 3px rgba(0,0,0,.06)' : 'none',
}}>
<Icon style={{ width: 12, height: 12, color: '#a78bfa' }} />
{label}
</div>
))}
</div>
{/* Detail panel */}
<AnimatePresence>
{active && (
<motion.div
initial={{ y: '100%' }}
animate={{ y: 0 }}
exit={{ y: '100%' }}
transition={{ duration: 0.3, ease: [0.2, 0.7, 0.2, 1] }}
style={{
position: 'absolute', left: 0, right: 0, bottom: 0,
background: isLight ? 'rgba(255,255,255,.98)' : 'rgba(15,10,31,.97)',
borderTop: `1px solid ${active.color}${isLight ? '30' : '40'}`,
zIndex: 50,
padding: '18px 24px 20px',
boxShadow: isLight ? '0 -8px 30px rgba(0,0,0,.08)' : '0 -20px 60px rgba(0,0,0,.55)',
}}
>
<div style={{ maxWidth: 900, margin: '0 auto' }}>
<div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16, marginBottom: 14 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<div style={{
width: 38, height: 38, borderRadius: 11, flexShrink: 0,
background: `linear-gradient(135deg, ${active.color}3a, ${active.color}10)`,
border: `1px solid ${active.color}66`,
display: 'flex', alignItems: 'center', justifyContent: 'center',
color: active.color,
}}>
<active.icon style={{ width: 19, height: 19 }} />
</div>
<div className="space-y-2">
{layer.items.map((item, iidx) => (
<div key={iidx} className="flex items-start gap-2">
<div className={`w-1.5 h-1.5 rounded-full mt-1.5 ${layer.color} bg-current opacity-50`} />
<div>
<span className="text-xs font-semibold text-white/80">{item.label}</span>
<span className="text-xs text-white/40 ml-2">{item.desc}</span>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
<span style={{ fontSize: 15, fontWeight: 600, color: isLight ? '#1a1a2e' : '#f5f3fc', letterSpacing: -0.2 }}>
{active.title}
</span>
<span style={{
fontSize: 9, padding: '2px 7px', borderRadius: 4,
background: `${active.color}18`, color: active.color,
border: `1px solid ${active.color}40`,
letterSpacing: 0.8, textTransform: 'uppercase' as const, fontWeight: 600,
}}>
{active.tier === 'product' ? (de ? 'Anwendung' : 'Application') :
active.tier === 'proxy' ? 'Gateway' :
(de ? 'Inferenz' : 'Inference')}
</span>
</div>
<div style={{ fontSize: 11.5, color: isLight ? '#64748b' : 'rgba(236,233,247,.5)', marginTop: 2 }}>
{active.subtitle}
</div>
</div>
</div>
<button
onClick={() => setActiveId(null)}
style={{
background: 'transparent',
border: `1px solid ${isLight ? 'rgba(0,0,0,.15)' : 'rgba(167,139,250,.25)'}`,
color: isLight ? '#64748b' : 'rgba(236,233,247,.5)',
width: 28, height: 28, borderRadius: 14,
cursor: 'pointer', display: 'flex',
alignItems: 'center', justifyContent: 'center', flexShrink: 0,
}}
>
<X style={{ width: 13, height: 13 }} />
</button>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 20 }}>
<div>
<div style={{ fontSize: 8.5, letterSpacing: 1.5, textTransform: 'uppercase' as const, color: isLight ? '#94a3b8' : 'rgba(236,233,247,.32)', marginBottom: 7, fontWeight: 600 }}>
{de ? 'Stack' : 'Tech Stack'}
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 5 }}>
{active.tech.map(tk => (
<span key={tk} style={{
...MONO,
fontSize: 10, padding: '3px 8px', borderRadius: 5,
background: isLight ? '#f1f5f9' : 'rgba(255,255,255,.05)',
border: `1px solid ${isLight ? 'rgba(0,0,0,.1)' : 'rgba(255,255,255,.1)'}`,
color: isLight ? '#334155' : 'rgba(236,233,247,.65)',
}}>{tk}</span>
))}
</div>
</div>
<div>
<div style={{ fontSize: 8.5, letterSpacing: 1.5, textTransform: 'uppercase' as const, color: isLight ? '#94a3b8' : 'rgba(236,233,247,.32)', marginBottom: 7, fontWeight: 600 }}>
{de ? 'Funktionen' : 'Capabilities'}
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
{active.services.map(s => (
<div key={s.name} style={{ display: 'flex', alignItems: 'baseline', gap: 6 }}>
<div style={{ width: 3, height: 3, borderRadius: '50%', background: active.color, opacity: 0.7, flexShrink: 0, marginTop: 6 }} />
<span style={{ fontSize: 11.5, fontWeight: 600, color: isLight ? '#1a1a2e' : 'rgba(245,243,252,.82)' }}>{s.name}</span>
<span style={{ fontSize: 10, color: isLight ? '#64748b' : 'rgba(236,233,247,.38)' }}>{s.desc}</span>
</div>
))}
</div>
</div>
</FadeInView>
)
})}
</div>
{/* Security Bar */}
<FadeInView delay={0.6}>
<GlassCard hover={false} className="p-4">
<div className="flex items-center justify-center gap-8 flex-wrap">
{securityFeatures.map((feat, idx) => {
const Icon = feat.icon
return (
<div key={idx} className="flex items-center gap-2">
<Icon className="w-4 h-4 text-emerald-400" />
<span className="text-xs text-white/60">{feat.label}</span>
</div>
)
})}
</motion.div>
)}
</AnimatePresence>
</div>
</GlassCard>
</FadeInView>
</div>
)
+126 -121
View File
@@ -1,5 +1,6 @@
'use client'
import { useEffect, useState } from 'react'
import { Language } from '@/lib/types'
import { t } from '@/lib/i18n'
import GradientText from '../ui/GradientText'
@@ -9,94 +10,135 @@ import { TrendingUp, TrendingDown, Minus } from 'lucide-react'
interface AssumptionsSlideProps {
lang: Language
investorId?: string | null
preferredScenarioId?: string | null
isWandeldarlehen?: boolean
}
export default function AssumptionsSlide({ lang }: AssumptionsSlideProps) {
interface SheetRow {
row_label?: string
values?: Record<string, number>
values_total?: Record<string, number>
}
interface ScenarioKPIs {
arr: number
customers: number
headcount: number
cash: number
breakEvenYear: string
}
function fmtArr(v: number, de: boolean): string {
if (v >= 1_000_000) return de ? `~${(v / 1_000_000).toFixed(1).replace('.', ',')} Mio. EUR` : `~EUR ${(v / 1_000_000).toFixed(1)}M`
if (v >= 1000) return de ? `~${Math.round(v / 1000)}k EUR` : `~EUR ${Math.round(v / 1000)}k`
return de ? `~${v} EUR` : `~EUR ${v}`
}
function fmtCash(v: number, de: boolean): string {
if (Math.abs(v) >= 1_000_000) return de ? `~${(v / 1_000_000).toFixed(1).replace('.', ',')} Mio. EUR` : `~EUR ${(v / 1_000_000).toFixed(1)}M`
return de ? `~${Math.round(v / 1000)}k EUR` : `~EUR ${Math.round(v / 1000)}k`
}
async function loadScenarioKPIs(scenarioId: string | null): Promise<ScenarioKPIs> {
const param = scenarioId ? `?scenarioId=${scenarioId}` : ''
try {
const [guvRes, liqRes, persRes, kundenRes] = await Promise.all([
fetch(`/api/finanzplan/guv${param}`, { cache: 'no-store' }),
fetch(`/api/finanzplan/liquiditaet${param}`, { cache: 'no-store' }),
fetch(`/api/finanzplan/personalkosten${param}`, { cache: 'no-store' }),
fetch(`/api/finanzplan/kunden${param}`, { cache: 'no-store' }),
])
const [guv, liq, pers, kunden] = await Promise.all([guvRes.json(), liqRes.json(), persRes.json(), kundenRes.json()])
const findGuv = (label: string) => (guv.rows || []).find((r: SheetRow) => (r.row_label || '').includes(label))
const findLiq = (label: string) => (liq.rows || []).find((r: SheetRow) => (r.row_label || '').includes(label))
const kundenGesamt = (kunden.rows || []).find((r: SheetRow) => r.row_label === 'Bestandskunden gesamt')
const ebit = findGuv('EBIT')?.values || {}
let breakEvenYear = '—'
for (const y of [2026, 2027, 2028, 2029, 2030]) {
if ((ebit[`y${y}`] || 0) > 0) { breakEvenYear = String(y); break }
}
return {
arr: findGuv('Umsatzerlöse')?.values?.y2030 || 0,
customers: kundenGesamt?.values?.m60 || 0,
headcount: (pers.rows || []).filter((r: SheetRow) => ((r.values_total || r.values)?.m60 || 0) > 0).length,
cash: findLiq('LIQUIDIT')?.values?.m60 || findLiq('LIQUIDITAET')?.values?.m60 || 0,
breakEvenYear,
}
} catch {
return { arr: 0, customers: 0, headcount: 0, cash: 0, breakEvenYear: '—' }
}
}
export default function AssumptionsSlide({ lang, isWandeldarlehen }: AssumptionsSlideProps) {
const i = t(lang)
const de = lang === 'de'
// 3 Cases abgeleitet aus dem Finanzplan (Base Case = aktuelle DB-Daten)
const cases = [
{
name: 'Bear Case',
icon: TrendingDown,
color: 'text-red-400',
bg: 'bg-red-500/10 border-red-500/20',
desc: de ? 'Langsames Wachstum, höhere Churn' : 'Slow growth, higher churn',
assumptions: de ? [
'Kundenwachstum 50% langsamer als Base',
'Churn Rate 8% pro Monat (Startups)',
'Durchschnittspreis 20% niedriger',
'Personalaufbau verzögert um 6 Monate',
'Serverkosten 150€ pro Kunde',
const [scenarioData, setScenarioData] = useState<{ bear: ScenarioKPIs; base: ScenarioKPIs; bull: ScenarioKPIs } | null>(null)
useEffect(() => {
async function load() {
const baseId = isWandeldarlehen ? 'c0000000-0000-0000-0000-000000000200' : null
const bearId = isWandeldarlehen ? 'd0000000-0000-0000-0000-000000000201' : 'd0000000-0000-0000-0000-000000000301'
const bullId = isWandeldarlehen ? 'd0000000-0000-0000-0000-000000000202' : 'd0000000-0000-0000-0000-000000000302'
const [bear, base, bull] = await Promise.all([loadScenarioKPIs(bearId), loadScenarioKPIs(baseId), loadScenarioKPIs(bullId)])
setScenarioData({ bear, base, bull })
}
load()
}, [isWandeldarlehen])
const bear = scenarioData?.bear || { arr: 0, customers: 0, headcount: 0, cash: 0, breakEvenYear: '—' }
const base = scenarioData?.base || { arr: 0, customers: 0, headcount: 0, cash: 0, breakEvenYear: '—' }
const bull = scenarioData?.bull || { arr: 0, customers: 0, headcount: 0, cash: 0, breakEvenYear: '—' }
const baseAssumptions = isWandeldarlehen
? (de ? [
'Kundenwachstum: 8% monatlich, ab m32: 15%',
'Mix: 60% Starter, 25% Professional, 15% Enterprise',
`Personalaufbau: 3→${base.headcount} Personen (lean)`,
'Serverkosten: 50€/Kunde + 300€ Basis',
] : [
'Customer growth 50% slower than base',
'Churn rate 8% per month (startups)',
'Average price 20% lower',
'Hiring delayed by 6 months',
'Server costs €150 per customer',
],
kpis: {
kunden2030: '~600',
arr2030: de ? '~4,2 Mio. EUR' : '~EUR 4.2M',
ma2030: '25',
breakEven: '2030',
cash2030: de ? '~0,5 Mio. EUR' : '~EUR 0.5M',
},
},
{
name: 'Base Case',
icon: Minus,
color: 'text-indigo-400',
bg: 'bg-indigo-500/10 border-indigo-500/20',
desc: de ? 'Aktueller Finanzplan' : 'Current financial plan',
assumptions: de ? [
'Kundenwachstum wie geplant (14→1.200)',
'Mix: 75% Startup, 15% KMU, 7% Mittel, 3% Enterprise',
'Customer growth: 8% monthly, from m32: 15%',
'Mix: 60% Starter, 25% Professional, 15% Enterprise',
`Hiring: 3→${base.headcount} people (lean)`,
'Server costs: €50/customer + €300 base',
])
: (de ? [
'Kundenwachstum wie geplant (5→1.200+)',
'Mix: 60% Starter, 25% Professional, 15% Enterprise',
'Personalaufbau 5→10→17→25→35',
'Serverkosten 100€ pro Kunde + 2.000€ Basis',
'Break-Even Mitte 2029',
] : [
'Customer growth as planned (14→1,200)',
'Mix: 75% startup, 15% SME, 7% mid, 3% enterprise',
'Customer growth as planned (5→1,200+)',
'Mix: 60% Starter, 25% Professional, 15% Enterprise',
'Hiring 5→10→17→25→35',
'Server costs €100 per customer + €2,000 base',
'Break-even mid 2029',
],
kpis: {
kunden2030: '~1.200',
arr2030: de ? '~10 Mio. EUR' : '~EUR 10M',
ma2030: '35',
breakEven: '2029',
cash2030: de ? '~6,4 Mio. EUR' : '~EUR 6.4M',
},
])
const cases = [
{
name: 'Bear Case', icon: TrendingDown, color: 'text-red-400', bg: 'bg-red-500/10 border-red-500/20',
desc: de ? 'Langsames Wachstum, höhere Churn' : 'Slow growth, higher churn',
assumptions: de
? ['Kundenwachstum 40% langsamer', 'Churn Rate 50% höher', 'Personalaufbau wie Base Case']
: ['Customer growth 40% slower', 'Churn rate 50% higher', 'Hiring same as base case'],
kpis: bear,
},
{
name: 'Bull Case',
icon: TrendingUp,
color: 'text-emerald-400',
bg: 'bg-emerald-500/10 border-emerald-500/20',
desc: de ? 'Beschleunigtes Wachstum' : 'Accelerated growth',
assumptions: de ? [
'Kundenwachstum 50% schneller (Regulierungsdruck)',
'Enterprise-Anteil steigt auf 8%',
'Durchschnittspreis 15% höher (Upselling)',
'Channel-Partner ab Q1/2027',
'EU-Expansion ab 2028',
] : [
'Customer growth 50% faster (regulation pressure)',
'Enterprise share rises to 8%',
'Average price 15% higher (upselling)',
'Channel partners from Q1/2027',
'EU expansion from 2028',
],
kpis: {
kunden2030: '~2.000',
arr2030: de ? '~18 Mio. EUR' : '~EUR 18M',
ma2030: '50',
breakEven: '2028',
cash2030: de ? '~15 Mio. EUR' : '~EUR 15M',
name: 'Base Case', icon: Minus, color: 'text-indigo-400', bg: 'bg-indigo-500/10 border-indigo-500/20',
desc: de ? 'Aktueller Finanzplan' : 'Current financial plan',
assumptions: baseAssumptions,
kpis: base,
},
{
name: 'Bull Case', icon: TrendingUp, color: 'text-emerald-400', bg: 'bg-emerald-500/10 border-emerald-500/20',
desc: de ? 'Beschleunigtes Wachstum' : 'Accelerated growth',
assumptions: de
? ['Kundenwachstum 30-50% schneller', 'Churn Rate 30% niedriger', 'Personalaufbau wie Base Case']
: ['Customer growth 30-50% faster', 'Churn rate 30% lower', 'Hiring same as base case'],
kpis: bull,
},
]
@@ -109,19 +151,17 @@ export default function AssumptionsSlide({ lang }: AssumptionsSlideProps) {
<p className="text-sm text-white/50 max-w-2xl mx-auto">{i.annex.assumptions.subtitle}</p>
</FadeInView>
{/* 3 Cases nebeneinander */}
<div className="grid md:grid-cols-3 gap-4 mb-6">
{cases.map((c, idx) => {
const Icon = c.icon
return (
<GlassCard key={idx} delay={0.1 + idx * 0.1} hover={false} className={`p-4 border-t-2 ${c.bg}`}>
<FadeInView key={idx} delay={0.1 + idx * 0.1}>
<GlassCard hover={false} className={`p-4 h-full ${c.bg} border`}>
<div className="flex items-center gap-2 mb-2">
<Icon className={`w-5 h-5 ${c.color}`} />
<h3 className={`text-sm font-bold ${c.color}`}>{c.name}</h3>
<h3 className={`text-base font-bold ${c.color}`}>{c.name}</h3>
</div>
<p className="text-[10px] text-white/40 mb-3">{c.desc}</p>
{/* Annahmen */}
<p className="text-xs text-white/40 mb-3">{c.desc}</p>
<div className="space-y-1.5 mb-4">
{c.assumptions.map((a, i) => (
<p key={i} className="text-xs text-white/60 pl-3 relative">
@@ -130,15 +170,13 @@ export default function AssumptionsSlide({ lang }: AssumptionsSlideProps) {
</p>
))}
</div>
{/* KPIs */}
<div className="border-t border-white/10 pt-3 space-y-1.5">
{[
{ label: de ? 'Kunden 2030' : 'Customers 2030', value: c.kpis.kunden2030 },
{ label: 'ARR 2030', value: c.kpis.arr2030 },
{ label: de ? 'Mitarbeiter 2030' : 'Employees 2030', value: c.kpis.ma2030 },
{ label: 'Break-Even', value: c.kpis.breakEven },
{ label: 'Cash 2030', value: c.kpis.cash2030 },
{ label: de ? 'Kunden 2030' : 'Customers 2030', value: `~${c.kpis.customers.toLocaleString('de-DE')}` },
{ label: 'ARR 2030', value: fmtArr(c.kpis.arr, de) },
{ label: de ? 'Mitarbeiter' : 'Employees', value: String(c.kpis.headcount) },
{ label: 'Break-Even', value: c.kpis.breakEvenYear },
{ label: 'Cash 2030', value: fmtCash(c.kpis.cash, de) },
].map((kpi, i) => (
<div key={i} className="flex justify-between text-xs">
<span className="text-white/40">{kpi.label}</span>
@@ -147,44 +185,11 @@ export default function AssumptionsSlide({ lang }: AssumptionsSlideProps) {
))}
</div>
</GlassCard>
</FadeInView>
)
})}
</div>
{/* Vergleichstabelle */}
<FadeInView delay={0.5}>
<GlassCard hover={false} className="p-4">
<h3 className="text-xs font-bold text-white/40 uppercase tracking-wider mb-3">
{de ? 'Szenario-Vergleich 2030' : 'Scenario Comparison 2030'}
</h3>
<table className="w-full text-xs">
<thead>
<tr className="border-b border-white/10">
<th className="text-left py-2 text-white/40"></th>
<th className="text-right py-2 text-red-400">Bear</th>
<th className="text-right py-2 text-indigo-400 font-bold">Base</th>
<th className="text-right py-2 text-emerald-400">Bull</th>
</tr>
</thead>
<tbody>
{[
{ label: de ? 'Kunden' : 'Customers', bear: '~600', base: '~1.200', bull: '~2.000' },
{ label: 'ARR', bear: de ? '~4,2 Mio.' : '~4.2M', base: de ? '~10 Mio.' : '~10M', bull: de ? '~18 Mio.' : '~18M' },
{ label: de ? 'Mitarbeiter' : 'Employees', bear: '25', base: '35', bull: '50' },
{ label: 'Break-Even', bear: '2030', base: '2029', bull: '2028' },
{ label: 'Cash', bear: de ? '~0,5 Mio.' : '~0.5M', base: de ? '~6,4 Mio.' : '~6.4M', bull: de ? '~15 Mio.' : '~15M' },
].map((row, idx) => (
<tr key={idx} className="border-b border-white/[0.03]">
<td className="py-1.5 text-white/60">{row.label}</td>
<td className="py-1.5 text-right text-red-400/70 font-mono">{row.bear}</td>
<td className="py-1.5 text-right text-indigo-300 font-mono font-bold">{row.base}</td>
<td className="py-1.5 text-right text-emerald-400/70 font-mono">{row.bull}</td>
</tr>
))}
</tbody>
</table>
</GlassCard>
</FadeInView>
</div>
)
}
@@ -5,101 +5,92 @@ import { t } from '@/lib/i18n'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
import GlassCard from '../ui/GlassCard'
import AnimatedCounter from '../ui/AnimatedCounter'
import { Repeat, TrendingUp, PiggyBank, ShieldCheck, Clock, Users } from 'lucide-react'
interface BusinessModelSlideProps {
lang: Language
products?: unknown[]
investorId?: string | null
preferredScenarioId?: string | null
isWandeldarlehen?: boolean
}
export default function BusinessModelSlide({ lang }: BusinessModelSlideProps) {
const i = t(lang)
const de = lang === 'de'
const tiers = [
{
name: 'Starter',
target: de ? 'Startups & Kleinstunternehmen' : 'Startups & Micro',
employees: '< 10',
price: de ? '3.600' : '3,600',
period: de ? 'EUR/Jahr' : 'EUR/yr',
features: de
? ['Code Security (SAST/DAST)', 'Compliance-Dokumente', 'Consent Management', '1 Anwendung']
: ['Code Security (SAST/DAST)', 'Compliance documents', 'Consent management', '1 application'],
highlight: false,
},
{
name: 'Professional',
target: de ? 'KMU & Mittelstand' : 'SME & Mid-Market',
employees: '10 250',
price: de ? '15.000 40.000' : '15,000 40,000',
period: de ? 'EUR/Jahr' : 'EUR/yr',
features: de
? ['Alle Module inkl. CE-Bewertung', 'Audit Manager End-to-End', 'AI Act Compliance (UCCA)', 'Unbegrenzte Anwendungen']
: ['All modules incl. CE assessment', 'Audit Manager end-to-end', 'AI Act Compliance (UCCA)', 'Unlimited applications'],
highlight: true,
},
{
name: 'Enterprise',
target: de ? 'Konzerne & OEMs' : 'Enterprises & OEMs',
employees: '250+',
price: de ? 'ab 50.000' : 'from 50,000',
period: de ? 'EUR/Jahr' : 'EUR/yr',
features: de
? ['Dedizierte Instanz', 'Custom Integrationen (SAP, MES)', 'SLA & Priority Support', 'Tender Matching & RFQ-Prüfung']
: ['Dedicated instance', 'Custom integrations (SAP, MES)', 'SLA & priority support', 'Tender matching & RFQ verification'],
highlight: false,
},
]
return (
<div>
<FadeInView className="text-center mb-10">
<div className="max-w-6xl mx-auto">
<FadeInView className="text-center mb-6">
<h2 className="text-4xl md:text-5xl font-bold mb-3">
<GradientText>{i.businessModel.title}</GradientText>
</h2>
<p className="text-lg text-white/50 max-w-2xl mx-auto">{i.businessModel.subtitle}</p>
<p className="text-lg text-white/50 max-w-2xl mx-auto">
{i.businessModel.subtitle}
</p>
</FadeInView>
{/* Key Metrics */}
<div className="grid md:grid-cols-3 gap-4 mb-8">
<GlassCard delay={0.2} className="text-center">
<Repeat className="w-6 h-6 text-indigo-400 mx-auto mb-2" />
<p className="text-sm text-white/50 mb-1">{i.businessModel.recurringRevenue}</p>
<p className="text-2xl font-bold text-white">100% SaaS</p>
<p className="text-xs text-white/30">{de ? 'Mitarbeiterbasiertes Pricing' : 'Employee-based pricing'}</p>
</GlassCard>
<GlassCard delay={0.3} className="text-center">
<TrendingUp className="w-6 h-6 text-green-400 mx-auto mb-2" />
<p className="text-sm text-white/50 mb-1">{i.businessModel.margin}</p>
<p className="text-2xl font-bold text-white">&gt;80%</p>
<p className="text-xs text-white/30">{de ? 'Cloud-native, keine HW-Kosten' : 'Cloud-native, no HW costs'}</p>
</GlassCard>
<GlassCard delay={0.4} className="text-center">
<PiggyBank className="w-6 h-6 text-amber-400 mx-auto mb-2" />
<p className="text-sm text-white/50 mb-1">{de ? 'Kundenersparnis' : 'Customer Savings'}</p>
<p className="text-2xl font-bold text-white">50.000+ EUR</p>
<p className="text-xs text-white/30">{de ? 'pro Kunde pro Jahr' : 'per customer per year'}</p>
</GlassCard>
<div className="grid grid-cols-3 gap-4">
{tiers.map((tier, idx) => (
<FadeInView key={idx} delay={0.1 + idx * 0.1}>
<GlassCard hover={false} className={`p-5 h-full ${tier.highlight ? 'border-slate-300/40 bg-gradient-to-b from-slate-200/[0.08] to-slate-400/[0.04] shadow-lg shadow-slate-300/10 ring-1 ring-slate-300/20' : ''}`}>
<h3 className="text-lg font-bold text-white mb-0.5">{tier.name}</h3>
<p className="text-sm text-white/40 mb-2">{tier.target}</p>
<p className="text-xs text-white/30 mb-4">{tier.employees} {de ? 'Mitarbeiter' : 'employees'}</p>
<div className="mb-4">
<span className="text-2xl font-black text-white">{tier.price}</span>
<span className="text-sm text-white/40 ml-1">{tier.period}</span>
</div>
{/* Savings Breakdown — the core argument */}
<FadeInView delay={0.5}>
<GlassCard hover={false} className="p-6">
<h3 className="text-lg font-semibold mb-5 text-white/70 text-center">
{de ? 'ROI-Rechnung: Kunde mit 250+ Mitarbeitern' : 'ROI Calculation: Customer with 250+ employees'}
</h3>
<div className="grid md:grid-cols-2 gap-8">
{/* Customer pays */}
<div>
<h4 className="text-xs font-bold text-indigo-400 uppercase tracking-wider mb-3">
{i.businessModel.customerPays}
</h4>
<div className="bg-indigo-500/10 border border-indigo-500/20 rounded-xl p-4">
<p className="text-3xl font-bold text-white text-center mb-1">
<AnimatedCounter target={40} suffix="-50k" duration={1500} /> EUR
</p>
<p className="text-xs text-white/40 text-center">{de ? 'pro Jahr, modular waehlbar' : 'per year, modular choice'}</p>
</div>
</div>
{/* Customer saves */}
<div>
<h4 className="text-xs font-bold text-emerald-400 uppercase tracking-wider mb-3">
{i.businessModel.customerSaves}
</h4>
<div className="space-y-2">
{[
{ label: i.businessModel.savingsPentest, amount: '30.000', icon: ShieldCheck },
{ label: i.businessModel.savingsCE, amount: '20.000', icon: Clock },
{ label: i.businessModel.savingsAudit, amount: '60.000+', icon: Users },
].map((item, idx) => (
<div key={idx} className="flex items-center justify-between bg-emerald-500/10 rounded-lg px-3 py-2">
<div className="flex items-center gap-2">
<item.icon className="w-3.5 h-3.5 text-emerald-400" />
<span className="text-xs text-white/70">{item.label}</span>
</div>
<span className="text-xs font-bold text-emerald-300">{item.amount} EUR</span>
</div>
<ul className="space-y-2">
{tier.features.map((f, i) => (
<li key={i} className="flex items-start gap-2 text-sm text-white/50">
<span className="w-1.5 h-1.5 rounded-full bg-indigo-400 mt-1.5 shrink-0" />
{f}
</li>
))}
<div className="flex items-center justify-between bg-emerald-500/20 border border-emerald-500/30 rounded-lg px-3 py-2">
<span className="text-xs font-bold text-white">{i.businessModel.savingsTotal}</span>
<span className="text-sm font-bold text-emerald-300">50.000 - 110.000+ EUR</span>
</div>
</div>
</div>
</div>
<p className="text-center text-xs text-white/30 mt-4">
{de
? '+ Strafvermeidung, Echtzeit-Kundenanfragen, kein Auditmanager noetig'
: '+ penalty avoidance, real-time customer inquiries, no audit manager needed'}
</p>
</ul>
</GlassCard>
</FadeInView>
))}
</div>
</div>
)
}
+14 -73
View File
@@ -1,6 +1,7 @@
'use client'
import { Language } from '@/lib/types'
import ProjectionFooter from '../ui/ProjectionFooter'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
import GlassCard from '../ui/GlassCard'
@@ -17,9 +18,9 @@ export default function CapTableSlide({ lang }: CapTableSlideProps) {
const de = lang === 'de'
const capTableData = [
{ name: 'Benjamin Bönisch (CEO)', value: 37.5, color: '#6366f1' },
{ name: 'Sharang Parnerkar (CTO)', value: 37.5, color: '#8b5cf6' },
{ name: de ? 'Pre-Seed Investor' : 'Pre-Seed Investor', value: 19.6, color: '#f59e0b' },
{ name: 'Benjamin Bönisch (CEO)', value: 37.3, color: '#6366f1' },
{ name: 'Sharang Parnerkar (CTO)', value: 37.3, color: '#8b5cf6' },
{ name: de ? 'Pre-Seed Investor' : 'Pre-Seed Investor', value: 20.0, color: '#f59e0b' },
{ name: 'ESOP Pool', value: 5.4, color: '#94a3b8' },
]
@@ -30,7 +31,7 @@ export default function CapTableSlide({ lang }: CapTableSlideProps) {
<GradientText>{de ? 'Investition & Cap Table' : 'Investment & Cap Table'}</GradientText>
</h2>
<p className="text-sm text-white/50 max-w-2xl mx-auto">
{de ? '4 Mio. EUR Pre-Money Bewertung · 975.000 EUR Pre-Seed · Gründung Jul/Aug 2026' : 'EUR 4M pre-money valuation · EUR 975,000 pre-seed · Founding Jul/Aug 2026'}
{de ? '4 Mio. EUR Pre-Money Bewertung · 1.000.000 EUR Pre-Seed · Gründung Aug 2026' : 'EUR 4M pre-money valuation · EUR 1,000,000 pre-seed · Founding Aug 2026'}
</p>
</FadeInView>
@@ -79,10 +80,9 @@ export default function CapTableSlide({ lang }: CapTableSlideProps) {
<div className="space-y-3">
{[
{ label: 'Pre-Money Bewertung', value: '4.000.000 EUR', icon: Target },
{ label: 'Investment', value: '975.000 EUR', icon: Briefcase },
{ label: 'Post-Money', value: '4.975.000 EUR', icon: TrendingUp },
{ label: de ? 'Investoranteil' : 'Investor Share', value: '19,6%', icon: Users },
{ label: 'Instrument', value: de ? 'Stammkapital + Wandeldarlehen' : 'Equity + Convertible', icon: Briefcase },
{ label: 'Investment', value: '1.000.000 EUR', icon: Briefcase },
{ label: 'Post-Money', value: '5.000.000 EUR', icon: TrendingUp },
{ label: de ? 'Investoranteil' : 'Investor Share', value: '20%', icon: Users },
].map((item, idx) => {
const Icon = item.icon
return (
@@ -100,66 +100,6 @@ export default function CapTableSlide({ lang }: CapTableSlideProps) {
</FadeInView>
</div>
{/* Gründergehälter + Gewinnverwendung */}
<div className="grid md:grid-cols-2 gap-6 mb-6">
<FadeInView delay={0.2}>
<GlassCard hover={false} className="p-5">
<h3 className="text-sm font-bold text-emerald-400 uppercase tracking-wider mb-3">
{de ? 'Gründergehälter' : 'Founder Salaries'}
</h3>
<p className="text-xs text-white/50 mb-3">
{de
? 'Bewusst unter Markt — zeigt Investoren Skin in the Game und Kapitaldisziplin.'
: 'Deliberately below market — shows investors skin in the game and capital discipline.'}
</p>
<div className="space-y-2">
{[
{ period: '2026 (AugDez)', salary: de ? '0 EUR (unbezahlt)' : 'EUR 0 (unpaid)', note: de ? 'Gründungsphase' : 'Founding phase' },
{ period: '2027', salary: '7.000 EUR/Mo', note: de ? 'Unter Marktniveau' : 'Below market' },
{ period: '2028', salary: '~8.000 EUR/Mo', note: de ? 'Product-Market Fit' : 'Product-market fit' },
{ period: '2029+', salary: '~9.100 EUR/Mo', note: de ? 'Break-Even erreicht' : 'Break-even reached' },
].map((item, idx) => (
<div key={idx} className="flex items-center justify-between text-xs">
<span className="text-white/40 min-w-[100px]">{item.period}</span>
<span className="text-white/70 font-mono">{item.salary}</span>
<span className="text-white/30 text-[10px]">{item.note}</span>
</div>
))}
</div>
</GlassCard>
</FadeInView>
<FadeInView delay={0.25}>
<GlassCard hover={false} className="p-5">
<h3 className="text-sm font-bold text-purple-400 uppercase tracking-wider mb-3">
{de ? 'Gewinnverwendung' : 'Use of Profits'}
</h3>
<p className="text-xs text-white/50 mb-3">
{de
? '100% Reinvestition in Wachstum — Standard bei schnell wachsenden SaaS-Startups.'
: '100% reinvestment in growth — standard for fast-growing SaaS startups.'}
</p>
<div className="space-y-2">
{(de ? [
'Keine Gewinnausschüttung bis mindestens Series A',
'Jeder Euro in Wachstum bringt 3-5x Return in 2-3 Jahren',
'Investition in: Engineering, Vertrieb, EU-Expansion',
'Gründer partizipieren über Equity-Wertsteigerung, nicht Gehalt',
] : [
'No profit distribution until at least Series A',
'Every euro in growth returns 3-5x in 2-3 years',
'Investment in: engineering, sales, EU expansion',
'Founders participate through equity appreciation, not salary',
]).map((item, idx) => (
<p key={idx} className="text-xs text-white/60 pl-3 relative">
<span className="absolute left-0 top-1 w-1.5 h-1.5 rounded-full bg-purple-400/60" />
{item}
</p>
))}
</div>
</GlassCard>
</FadeInView>
</div>
{/* ESOP + INVEST + Series A */}
<div className="grid md:grid-cols-3 gap-4 mb-6">
@@ -183,12 +123,12 @@ export default function CapTableSlide({ lang }: CapTableSlideProps) {
</h3>
<p className="text-xs text-white/60 leading-relaxed mb-2">
{de
? 'Das BAFA-Programm "INVEST — Zuschuss für Wagniskapital" erstattet Business Angels 20% ihres Investments als nicht rückzahlbaren Zuschuss.'
: 'The BAFA program "INVEST — Venture Capital Grant" reimburses business angels 20% of their investment as a non-repayable grant.'}
? 'Das BAFA-Programm "INVEST — Zuschuss für Wagniskapital" erstattet Business Angels bis zu 15% ihres Investments als steuerfreien Zuschuss plus 25% Exit-Zuschuss.'
: 'The BAFA program "INVEST — Venture Capital Grant" reimburses business angels up to 15% of their investment as a tax-free grant plus 25% exit grant.'}
</p>
<div className="space-y-1 text-xs">
<div className="flex justify-between"><span className="text-white/40">{de ? 'Zuschuss' : 'Grant'}</span><span className="text-emerald-300 font-bold">20%</span></div>
<div className="flex justify-between"><span className="text-white/40">{de ? 'Bei 975k Investment' : 'On EUR 975k'}</span><span className="text-emerald-300 font-bold">195.000 EUR</span></div>
<div className="flex justify-between"><span className="text-white/40">{de ? 'Erwerbszuschuss' : 'Acquisition grant'}</span><span className="text-emerald-300 font-bold">15%</span></div>
<div className="flex justify-between"><span className="text-white/40">{de ? 'Exit-Zuschuss' : 'Exit grant'}</span><span className="text-emerald-300 font-bold">25%</span></div>
<div className="flex justify-between"><span className="text-white/40">{de ? 'Max. pro Investor/Jahr' : 'Max. per investor/yr'}</span><span className="text-white/60">500.000 EUR</span></div>
<div className="flex justify-between"><span className="text-white/40">{de ? 'Haltefrist' : 'Holding period'}</span><span className="text-white/60">{de ? '3 Jahre' : '3 years'}</span></div>
</div>
@@ -198,7 +138,7 @@ export default function CapTableSlide({ lang }: CapTableSlideProps) {
<FadeInView delay={0.4}>
<GlassCard hover={false} className="p-4">
<h3 className="text-xs font-bold text-blue-400 uppercase tracking-wider mb-2">
{de ? 'Series A Ausblick (Q1/Q2 2028)' : 'Series A Outlook (Q1/Q2 2028)'}
{de ? 'Series A Ausblick (Optional)' : 'Series A Outlook (Optional)'}
</h3>
<div className="space-y-1.5 text-xs">
<div className="flex justify-between"><span className="text-white/40">{de ? 'Bewertung' : 'Valuation'}</span><span className="text-white/70 font-mono">15-25 Mio. EUR</span></div>
@@ -212,6 +152,7 @@ export default function CapTableSlide({ lang }: CapTableSlideProps) {
</GlassCard>
</FadeInView>
</div>
<ProjectionFooter lang={lang} />
</div>
)
}
+160 -155
View File
@@ -54,7 +54,7 @@ const EXTENDED_COMPETITORS: ExtendedCompetitor[] = [
revenue: '$220M ARR',
revenueNum: 220_000_000,
customers: 12000,
customerCountries: '58 Laender',
customerCountries: '58 Länder',
fundingTotal: '$504M',
fundingRound: 'Series D ($150M, $4.15B val.)',
investors: ['Sequoia Capital', 'Wellington Mgmt', 'Craft Ventures', 'CrowdStrike', 'Goldman Sachs', 'Y Combinator'],
@@ -75,7 +75,7 @@ const EXTENDED_COMPETITORS: ExtendedCompetitor[] = [
revenue: '$100M ARR',
revenueNum: 100_000_000,
customers: 8000,
customerCountries: '80+ Laender',
customerCountries: '80+ Länder',
fundingTotal: '$328M',
fundingRound: 'Series C ($200M, $2B val.)',
investors: ['ICONIQ Growth', 'GGV Capital', 'Salesforce Ventures', 'SentinelOne'],
@@ -96,7 +96,7 @@ const EXTENDED_COMPETITORS: ExtendedCompetitor[] = [
revenue: '$38M ARR',
revenueNum: 38_000_000,
customers: 3000,
customerCountries: '75+ Laender',
customerCountries: '75+ Länder',
fundingTotal: '$32M',
fundingRound: 'Series B ($20M, 2024)',
investors: ['Accel', 'Elevation Capital', 'Blume Ventures'],
@@ -138,7 +138,7 @@ const EXTENDED_COMPETITORS: ExtendedCompetitor[] = [
revenue: '~€52M',
revenueNum: 52_000_000,
customers: 4000,
customerCountries: '50+ Laender',
customerCountries: '50+ Länder',
fundingTotal: '€80M',
fundingRound: 'Series B (€61M, €341M val.)',
investors: ['Morgan Stanley Expansion', 'One Peak Partners'],
@@ -187,64 +187,68 @@ interface ComparisonFeature {
heydata: FeatureStatus
isDiff: boolean
isUSP: boolean
group?: string
}
const ALL_FEATURES: ComparisonFeature[] = [
// Top 5 Differentiators (isDiff=true) — no other vendor has ANY of these
{ de: 'Self-Hosted / On-Premise', en: 'Self-Hosted / On-Premise', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: true, isUSP: true },
{ de: 'Code-Security & DevSecOps (6 Tools)', en: 'Code Security & DevSecOps (6 Tools)', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: true, isUSP: true },
{ de: '110 Gesetze & Regularien, 25.000+ Sicherheitskontrollen', en: '110 Laws & Regulations, 25,000+ Security Controls', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: true, isUSP: true },
{ de: 'Hardware-Moat (Mac Mini/Studio)', en: 'Hardware Moat (Mac Mini/Studio)', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: true, isUSP: true },
{ de: 'PII-Redaction LLM Gateway', en: 'PII Redaction LLM Gateway', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: true, isUSP: true },
// More USPs
{ de: 'IPFS Dokumenten-Archivierung', en: 'IPFS Document Archiving', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: false, isUSP: true },
{ de: 'SBOM-Generator (CycloneDX/SPDX)', en: 'SBOM Generator (CycloneDX/SPDX)', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: false, isUSP: true },
{ de: 'Multi-Framework Consent SDK', en: 'Multi-Framework Consent SDK', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: false, isUSP: true },
{ de: 'RAG mit 25.000+ Sicherheitskontrollen', en: 'RAG with 25,000+ Security Controls', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: false, isUSP: true },
// Pentesting & Code-Security (kein Compliance-Wettbewerber hat dies)
{ de: 'SAST (Static Application Security Testing)', en: 'SAST (Static Application Security Testing)', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: false, isUSP: true },
{ de: 'DAST (Dynamic Application Security Testing)', en: 'DAST (Dynamic Application Security Testing)', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: false, isUSP: true },
{ de: 'LLM-Auto-Fix (automatische Code-Korrekturen)', en: 'LLM Auto-Fix (Automatic Code Corrections)', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: false, isUSP: true },
{ de: 'Container-Security Scanning (Trivy)', en: 'Container Security Scanning (Trivy)', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: false, isUSP: true },
{ de: 'Secret Detection (Gitleaks)', en: 'Secret Detection (Gitleaks)', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: false, isUSP: true },
// Compliance Features (shared)
{ de: 'DSGVO / GDPR', en: 'GDPR', bp: true, vanta: 'partial', drata: 'partial', sprinto: 'partial', proliance: true, dataguard: true, heydata: true, isDiff: false, isUSP: false },
{ de: 'AI Act', en: 'AI Act', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: false, isUSP: false },
{ de: 'Cyber Resilience Act (CRA)', en: 'Cyber Resilience Act (CRA)', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: false, isUSP: false },
{ de: 'NIS2-Richtlinie', en: 'NIS2 Directive', bp: true, vanta: false, drata: 'partial', sprinto: false, proliance: false, dataguard: 'partial', heydata: false, isDiff: false, isUSP: false },
{ de: 'SOC 2', en: 'SOC 2', bp: 'partial', vanta: true, drata: true, sprinto: true, proliance: false, dataguard: true, heydata: false, isDiff: false, isUSP: false },
{ de: 'ISO 27001', en: 'ISO 27001', bp: true, vanta: true, drata: true, sprinto: true, proliance: false, dataguard: true, heydata: false, isDiff: false, isUSP: false },
{ de: 'HIPAA', en: 'HIPAA', bp: false, vanta: true, drata: true, sprinto: true, proliance: false, dataguard: false, heydata: false, isDiff: false, isUSP: false },
{ de: 'TISAX', en: 'TISAX', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: true, heydata: false, isDiff: false, isUSP: false },
{ de: 'HinSchG (Whistleblower)', en: 'HinSchG (Whistleblower)', bp: true, vanta: false, drata: false, sprinto: false, proliance: 'partial', dataguard: false, heydata: false, isDiff: false, isUSP: false },
// Functional Features
{ de: 'VVT (Art. 30 DSGVO)', en: 'Records of Processing (Art. 30)', bp: true, vanta: false, drata: false, sprinto: false, proliance: true, dataguard: true, heydata: true, isDiff: false, isUSP: false },
{ de: 'TOM-Dokumentation', en: 'TOM Documentation', bp: true, vanta: false, drata: false, sprinto: false, proliance: true, dataguard: true, heydata: 'partial', isDiff: false, isUSP: false },
{ de: 'DSFA (Art. 35 DSGVO)', en: 'DPIA (Art. 35 GDPR)', bp: true, vanta: false, drata: false, sprinto: false, proliance: true, dataguard: true, heydata: false, isDiff: false, isUSP: false },
{ de: 'Loeschkonzept / Loeschfristen', en: 'Deletion Concept / Retention', bp: true, vanta: false, drata: false, sprinto: false, proliance: 'partial', dataguard: 'partial', heydata: false, isDiff: false, isUSP: false },
{ de: 'Auftragsverarbeiter-Mgmt', en: 'Vendor/Processor Management', bp: true, vanta: true, drata: true, sprinto: 'partial', proliance: true, dataguard: true, heydata: 'partial', isDiff: false, isUSP: false },
{ de: 'Consent Management', en: 'Consent Management', bp: true, vanta: false, drata: false, sprinto: false, proliance: 'partial', dataguard: false, heydata: 'partial', isDiff: false, isUSP: false },
{ de: 'Betroffenenrechte (DSR)', en: 'Data Subject Requests', bp: true, vanta: false, drata: false, sprinto: false, proliance: true, dataguard: true, heydata: 'partial', isDiff: false, isUSP: false },
{ de: 'Risikobewertung', en: 'Risk Assessment', bp: true, vanta: true, drata: true, sprinto: true, proliance: 'partial', dataguard: true, heydata: false, isDiff: false, isUSP: false },
{ de: 'Audit-Management', en: 'Audit Management', bp: true, vanta: true, drata: true, sprinto: true, proliance: false, dataguard: true, heydata: false, isDiff: false, isUSP: false },
{ de: 'Schulungs-Management', en: 'Training Management', bp: true, vanta: 'partial', drata: 'partial', sprinto: 'partial', proliance: false, dataguard: 'partial', heydata: false, isDiff: false, isUSP: false },
{ de: 'Policy-Generator', en: 'Policy Generator', bp: true, vanta: true, drata: true, sprinto: 'partial', proliance: true, dataguard: true, heydata: 'partial', isDiff: false, isUSP: false },
{ de: 'Incident Response', en: 'Incident Response', bp: true, vanta: true, drata: true, sprinto: true, proliance: false, dataguard: 'partial', heydata: false, isDiff: false, isUSP: false },
// Technical Features
{ de: 'KI-gestuetzte Analyse', en: 'AI-Powered Analysis', bp: true, vanta: true, drata: true, sprinto: 'partial', proliance: false, dataguard: true, heydata: false, isDiff: false, isUSP: false },
{ de: 'Automatische Evidence-Sammlung', en: 'Automatic Evidence Collection', bp: true, vanta: true, drata: true, sprinto: true, proliance: false, dataguard: 'partial', heydata: false, isDiff: false, isUSP: false },
{ de: 'Continuous Monitoring', en: 'Continuous Monitoring', bp: true, vanta: true, drata: true, sprinto: true, proliance: false, dataguard: true, heydata: false, isDiff: false, isUSP: false },
{ de: 'Integrations (Slack, Jira, etc.)', en: 'Integrations (Slack, Jira, etc.)', bp: 'partial', vanta: true, drata: true, sprinto: true, proliance: false, dataguard: 'partial', heydata: false, isDiff: false, isUSP: false },
{ de: 'API / SDK', en: 'API / SDK', bp: true, vanta: true, drata: true, sprinto: 'partial', proliance: false, dataguard: 'partial', heydata: false, isDiff: false, isUSP: false },
{ de: 'Datensouveraenitaet (EU)', en: 'Data Sovereignty (EU)', bp: true, vanta: false, drata: false, sprinto: false, proliance: true, dataguard: true, heydata: true, isDiff: false, isUSP: false },
{ de: 'Mehrmandantenfaehig', en: 'Multi-Tenancy', bp: true, vanta: true, drata: true, sprinto: true, proliance: 'partial', dataguard: true, heydata: false, isDiff: false, isUSP: false },
{ de: 'Data Mapping / Datenfluss', en: 'Data Mapping / Data Flow', bp: true, vanta: 'partial', drata: 'partial', sprinto: false, proliance: false, dataguard: 'partial', heydata: false, isDiff: false, isUSP: false },
{ de: 'Cookie-Banner Generator', en: 'Cookie Banner Generator', bp: true, vanta: false, drata: false, sprinto: false, proliance: 'partial', dataguard: false, heydata: 'partial', isDiff: false, isUSP: false },
{ de: 'Dokument-Generator (61 Vorlagen)', en: 'Document Generator (61 Templates)', bp: true, vanta: 'partial', drata: 'partial', sprinto: false, proliance: 'partial', dataguard: 'partial', heydata: false, isDiff: false, isUSP: false },
{ de: 'Whistleblower-Portal', en: 'Whistleblower Portal', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: false, isUSP: false },
{ de: 'Maschinenbau-Branchenfokus', en: 'Manufacturing Industry Focus', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: false, isUSP: false },
{ de: 'Firmware & Embedded-Security', en: 'Firmware & Embedded Security', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: false, isUSP: false },
{ de: 'Autonomer KI-Support-Agent', en: 'Autonomous AI Support Agent', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: false, isUSP: false },
// ── Code Security & DevSecOps ──
{ de: 'Code-Security & DevSecOps (6 Tools)', en: 'Code Security & DevSecOps (6 Tools)', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: true, isUSP: true, group: 'code-security' },
{ de: 'SAST (Static Application Security Testing)', en: 'SAST (Static Application Security Testing)', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: true, isUSP: true, group: 'code-security' },
{ de: 'DAST (Dynamic Application Security Testing)', en: 'DAST (Dynamic Application Security Testing)', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: true, isUSP: true, group: 'code-security' },
{ de: 'SBOM-Generator (CycloneDX/SPDX)', en: 'SBOM Generator (CycloneDX/SPDX)', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: true, isUSP: true, group: 'code-security' },
{ de: 'Container-Security Scanning (Trivy)', en: 'Container Security Scanning (Trivy)', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: true, isUSP: true, group: 'code-security' },
{ de: 'Secret Detection (Gitleaks)', en: 'Secret Detection (Gitleaks)', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: true, isUSP: true, group: 'code-security' },
{ de: 'LLM-Auto-Fix (automatische Code-Korrekturen)', en: 'LLM Auto-Fix (Automatic Code Corrections)', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: true, isUSP: true, group: 'code-security' },
{ de: 'Firmware & Embedded-Security', en: 'Firmware & Embedded Security', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: true, isUSP: true, group: 'code-security' },
// ── KI & Daten ──
{ de: 'PII-Redaction LLM Gateway', en: 'PII Redaction LLM Gateway', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: true, isUSP: true, group: 'ai-data' },
{ de: 'RAG mit 25.000+ Sicherheitskontrollen', en: 'RAG with 25,000+ Security Controls', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: true, isUSP: true, group: 'ai-data' },
{ de: 'Autonomer KI-Support-Agent', en: 'Autonomous AI Support Agent', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: true, isUSP: true, group: 'ai-data' },
{ de: 'KI-gestützte Analyse', en: 'AI-Powered Analysis', bp: true, vanta: true, drata: true, sprinto: 'partial', proliance: false, dataguard: true, heydata: false, isDiff: false, isUSP: false, group: 'ai-data' },
// ── Regulatorische Frameworks ──
{ de: 'DSGVO / GDPR', en: 'GDPR', bp: true, vanta: 'partial', drata: 'partial', sprinto: 'partial', proliance: true, dataguard: true, heydata: true, isDiff: false, isUSP: false, group: 'frameworks' },
{ de: 'AI Act', en: 'AI Act', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: true, isUSP: true, group: 'frameworks' },
{ de: 'Cyber Resilience Act (CRA)', en: 'Cyber Resilience Act (CRA)', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: true, isUSP: true, group: 'frameworks' },
{ de: 'NIS2-Richtlinie', en: 'NIS2 Directive', bp: true, vanta: false, drata: 'partial', sprinto: false, proliance: false, dataguard: 'partial', heydata: false, isDiff: false, isUSP: false, group: 'frameworks' },
{ de: 'SOC 2', en: 'SOC 2', bp: 'partial', vanta: true, drata: true, sprinto: true, proliance: false, dataguard: true, heydata: false, isDiff: false, isUSP: false, group: 'frameworks' },
{ de: 'ISO 27001', en: 'ISO 27001', bp: true, vanta: true, drata: true, sprinto: true, proliance: false, dataguard: true, heydata: false, isDiff: false, isUSP: false, group: 'frameworks' },
{ de: 'HIPAA', en: 'HIPAA', bp: false, vanta: true, drata: true, sprinto: true, proliance: false, dataguard: false, heydata: false, isDiff: false, isUSP: false, group: 'frameworks' },
{ de: 'TISAX', en: 'TISAX', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: true, heydata: false, isDiff: false, isUSP: false, group: 'frameworks' },
{ de: 'HinSchG (Whistleblower)', en: 'HinSchG (Whistleblower)', bp: true, vanta: false, drata: false, sprinto: false, proliance: 'partial', dataguard: false, heydata: false, isDiff: false, isUSP: false, group: 'frameworks' },
// ── Compliance-Dokumentation ──
{ de: 'VVT (Art. 30 DSGVO)', en: 'Records of Processing (Art. 30)', bp: true, vanta: false, drata: false, sprinto: false, proliance: true, dataguard: true, heydata: true, isDiff: false, isUSP: false, group: 'documentation' },
{ de: 'TOM-Dokumentation', en: 'TOM Documentation', bp: true, vanta: false, drata: false, sprinto: false, proliance: true, dataguard: true, heydata: 'partial', isDiff: false, isUSP: false, group: 'documentation' },
{ de: 'DSFA (Art. 35 DSGVO)', en: 'DPIA (Art. 35 GDPR)', bp: true, vanta: false, drata: false, sprinto: false, proliance: true, dataguard: true, heydata: false, isDiff: false, isUSP: false, group: 'documentation' },
{ de: 'Löschkonzept / Löschfristen', en: 'Deletion Concept / Retention', bp: true, vanta: false, drata: false, sprinto: false, proliance: 'partial', dataguard: 'partial', heydata: false, isDiff: false, isUSP: false, group: 'documentation' },
{ de: 'Policy-Generator', en: 'Policy Generator', bp: true, vanta: true, drata: true, sprinto: 'partial', proliance: true, dataguard: true, heydata: 'partial', isDiff: false, isUSP: false, group: 'documentation' },
{ de: 'Dokument-Generator (61 Vorlagen)', en: 'Document Generator (61 Templates)', bp: true, vanta: 'partial', drata: 'partial', sprinto: false, proliance: 'partial', dataguard: 'partial', heydata: false, isDiff: false, isUSP: false, group: 'documentation' },
// ── Operative Compliance ──
{ de: 'Audit-Management', en: 'Audit Management', bp: true, vanta: true, drata: true, sprinto: true, proliance: false, dataguard: true, heydata: false, isDiff: false, isUSP: false, group: 'operations' },
{ de: 'Risikobewertung', en: 'Risk Assessment', bp: true, vanta: true, drata: true, sprinto: true, proliance: 'partial', dataguard: true, heydata: false, isDiff: false, isUSP: false, group: 'operations' },
{ de: 'Incident Response', en: 'Incident Response', bp: true, vanta: true, drata: true, sprinto: true, proliance: false, dataguard: 'partial', heydata: false, isDiff: false, isUSP: false, group: 'operations' },
{ de: 'Consent Management', en: 'Consent Management', bp: true, vanta: false, drata: false, sprinto: false, proliance: 'partial', dataguard: false, heydata: 'partial', isDiff: false, isUSP: false, group: 'operations' },
{ de: 'Betroffenenrechte (DSR)', en: 'Data Subject Requests', bp: true, vanta: false, drata: false, sprinto: false, proliance: true, dataguard: true, heydata: 'partial', isDiff: false, isUSP: false, group: 'operations' },
{ de: 'Auftragsverarbeiter-Mgmt', en: 'Vendor/Processor Management', bp: true, vanta: true, drata: true, sprinto: 'partial', proliance: true, dataguard: true, heydata: 'partial', isDiff: false, isUSP: false, group: 'operations' },
{ de: 'Schulungs-Management', en: 'Training Management', bp: true, vanta: 'partial', drata: 'partial', sprinto: 'partial', proliance: false, dataguard: 'partial', heydata: false, isDiff: false, isUSP: false, group: 'operations' },
{ de: 'Whistleblower-Portal', en: 'Whistleblower Portal', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: true, isUSP: true, group: 'operations' },
// ── Technische Plattform ──
{ de: 'Continuous Monitoring', en: 'Continuous Monitoring', bp: true, vanta: true, drata: true, sprinto: true, proliance: false, dataguard: true, heydata: false, isDiff: false, isUSP: false, group: 'platform' },
{ de: 'Automatische Evidence-Sammlung', en: 'Automatic Evidence Collection', bp: true, vanta: true, drata: true, sprinto: true, proliance: false, dataguard: 'partial', heydata: false, isDiff: false, isUSP: false, group: 'platform' },
{ de: 'API / SDK', en: 'API / SDK', bp: true, vanta: true, drata: true, sprinto: 'partial', proliance: false, dataguard: 'partial', heydata: false, isDiff: false, isUSP: false, group: 'platform' },
{ de: 'Integrations (Slack, Jira, etc.)', en: 'Integrations (Slack, Jira, etc.)', bp: 'partial', vanta: true, drata: true, sprinto: true, proliance: false, dataguard: 'partial', heydata: false, isDiff: false, isUSP: false, group: 'platform' },
{ de: 'Datensouveraenitaet (EU)', en: 'Data Sovereignty (EU)', bp: true, vanta: false, drata: false, sprinto: false, proliance: true, dataguard: true, heydata: true, isDiff: false, isUSP: false, group: 'platform' },
{ de: 'Mehrmandantenfähig', en: 'Multi-Tenancy', bp: true, vanta: true, drata: true, sprinto: true, proliance: 'partial', dataguard: true, heydata: false, isDiff: false, isUSP: false, group: 'platform' },
{ de: 'Data Mapping / Datenfluss', en: 'Data Mapping / Data Flow', bp: true, vanta: 'partial', drata: 'partial', sprinto: false, proliance: false, dataguard: 'partial', heydata: false, isDiff: false, isUSP: false, group: 'platform' },
{ de: 'Cookie-Banner Generator', en: 'Cookie Banner Generator', bp: true, vanta: false, drata: false, sprinto: false, proliance: 'partial', dataguard: false, heydata: 'partial', isDiff: false, isUSP: false, group: 'platform' },
{ de: 'IPFS/Blockchain (optional)', en: 'IPFS/Blockchain (optional)', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: true, isUSP: true, group: 'platform' },
// ── Branche & Spezial ──
{ de: 'Maschinenbau-Branchenfokus', en: 'Manufacturing Industry Focus', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: true, isUSP: true, group: 'industry' },
]
// ─── DACH Landscape Note ───────────────────────────────────────────────────────
@@ -277,13 +281,12 @@ const PRICING_COMPARISON: CompetitorPricing[] = [
{
name: 'ComplAI',
flag: '🇩🇪',
model: 'Cloud (BSI DE / OVH FR)',
model: 'Cloud (BSI DE)',
publicPricing: true,
tiers: [
{ name: { de: 'Startup/<10', en: 'Startup/<10' }, price: 'ab €300/mo', annual: 'ab €3.600/yr', notes: { de: '14-Tage-Test, Kreditkarte', en: '14-day trial, credit card' } },
{ name: { de: '10-50 MA', en: '10-50 emp.' }, price: 'ab €1.250/mo', annual: 'ab €15.000/yr', notes: { de: 'Cloud, modular, 110 Regularien', en: 'Cloud, modular, 110 regulations' } },
{ name: { de: '50-250 MA', en: '50-250 emp.' }, price: 'ab €2.500/mo', annual: 'ab €30.000/yr', notes: { de: 'Cloud, alle Module, Priority', en: 'Cloud, all modules, priority' } },
{ name: { de: '250+ MA', en: '250+ emp.' }, price: 'ab €3.500/mo', annual: 'ab €40.000/yr', notes: { de: 'Cloud, Enterprise, Dedicated', en: 'Cloud, enterprise, dedicated' } },
{ name: { de: 'Starter (<10 MA)', en: 'Starter (<10 emp.)' }, price: '€300/mo', annual: '€3.600/yr', notes: { de: '380+ Regularien, modular', en: '380+ regulations, modular' } },
{ name: { de: 'Professional (10-250)', en: 'Professional (10-250)' }, price: '€1.2503.333/mo', annual: '€15.00040.000/yr', notes: { de: 'Alle Module, Priority Support', en: 'All modules, priority support' } },
{ name: { de: 'Enterprise (250+)', en: 'Enterprise (250+)' }, price: 'ab €4.167/mo', annual: 'ab €50.000/yr', notes: { de: 'Dedicated, Custom, SLA', en: 'Dedicated, custom, SLA' } },
],
setupFee: '€0',
isBP: true,
@@ -405,15 +408,7 @@ interface AppSecFeature {
}
const APPSEC_FEATURES: AppSecFeature[] = [
// ComplAI USPs — kein AppSec-Anbieter hat dies
{ de: 'DSGVO / GDPR Compliance', en: 'GDPR Compliance', bp: true, snyk: false, veracode: false, checkmarx: false, sonar: false, semgrep: false, pentera: false, invicti: false, intruder: false, isUSP: true },
{ de: 'AI Act Compliance', en: 'AI Act Compliance', bp: true, snyk: false, veracode: false, checkmarx: false, sonar: false, semgrep: false, pentera: false, invicti: false, intruder: false, isUSP: true },
{ de: 'CRA & NIS2 Compliance', en: 'CRA & NIS2 Compliance', bp: true, snyk: false, veracode: false, checkmarx: false, sonar: false, semgrep: false, pentera: false, invicti: false, intruder: false, isUSP: true },
{ de: '57 Compliance-Module (SDK)', en: '57 Compliance Modules (SDK)', bp: true, snyk: false, veracode: false, checkmarx: false, sonar: false, semgrep: false, pentera: false, invicti: false, intruder: false, isUSP: true },
{ de: 'Self-Hosted KI (On-Premise)', en: 'Self-Hosted AI (On-Premise)', bp: true, snyk: false, veracode: false, checkmarx: false, sonar: false, semgrep: false, pentera: false, invicti: false, intruder: false, isUSP: true },
{ de: 'PII-Redaction LLM Gateway', en: 'PII Redaction LLM Gateway', bp: true, snyk: false, veracode: false, checkmarx: false, sonar: false, semgrep: false, pentera: false, invicti: false, intruder: false, isUSP: true },
{ de: 'Firmware & Embedded-Security', en: 'Firmware & Embedded Security', bp: true, snyk: false, veracode: 'partial', checkmarx: false, sonar: false, semgrep: false, pentera: false, invicti: false, intruder: false, isUSP: true },
// Shared AppSec Features
// Pure AppSec Features only (Compliance USPs removed — belong on Compliance tabs)
{ de: 'SAST (Static Analysis)', en: 'SAST (Static Analysis)', bp: true, snyk: true, veracode: true, checkmarx: true, sonar: true, semgrep: true, pentera: false, invicti: false, intruder: false, isUSP: false },
{ de: 'DAST (Dynamic Analysis)', en: 'DAST (Dynamic Analysis)', bp: true, snyk: false, veracode: true, checkmarx: true, sonar: false, semgrep: false, pentera: true, invicti: true, intruder: true, isUSP: false },
{ de: 'SCA (Software Composition)', en: 'SCA (Software Composition)', bp: true, snyk: true, veracode: true, checkmarx: true, sonar: 'partial', semgrep: 'partial', pentera: false, invicti: false, intruder: false, isUSP: false },
@@ -526,7 +521,7 @@ export default function CompetitionSlide({ lang, features, competitors }: Compet
{/* Tab Bar */}
<FadeInView delay={0.15} className="flex justify-center gap-2 mb-4 flex-wrap">
{([
{ key: 'overview' as ViewTab, de: 'Ueberblick & Vergleich', en: 'Overview & Comparison' },
{ key: 'overview' as ViewTab, de: 'Überblick & Vergleich', en: 'Overview & Comparison' },
{ key: 'features' as ViewTab, de: 'Feature-Matrix (Detail)', en: 'Feature Matrix (Detail)' },
{ key: 'pricing' as ViewTab, de: 'Pricing-Vergleich', en: 'Pricing Comparison' },
{ key: 'appsec' as ViewTab, de: 'Pentesting & AppSec', en: 'Pentesting & AppSec' },
@@ -537,7 +532,7 @@ export default function CompetitionSlide({ lang, features, competitors }: Compet
className={`px-4 py-1.5 rounded-full text-xs font-medium transition-all ${
activeTab === tab.key
? 'bg-indigo-500/20 text-indigo-300 border border-indigo-500/30'
: 'bg-white/[0.04] text-white/40 border border-white/5 hover:bg-white/[0.08]'
: 'bg-white/[0.04] text-white/40 border border-white/5 hover:bg-white/[0.08] animate-[pulse_3s_ease-in-out_infinite]'
}`}
>
{lang === 'de' ? tab.de : tab.en}
@@ -630,20 +625,6 @@ export default function CompetitionSlide({ lang, features, competitors }: Compet
{activeTab === 'features' && (
<FadeInView delay={0.2}>
<div className="space-y-2">
{/* Top 5 Differences */}
<div>
<SectionHeader
label={lang === 'de' ? 'Top 5 Unterschiede' : 'Top 5 Differences'}
count={top5.length}
open={openSections.has('top5')}
onToggle={() => toggleSection('top5')}
accent="text-yellow-400"
/>
{openSections.has('top5') && (
<FeatureTable features={top5} lang={lang} cols={competitorCols} labels={competitorLabels} highlight />
)}
</div>
{/* All Features */}
<div>
<SectionHeader
@@ -660,7 +641,7 @@ export default function CompetitionSlide({ lang, features, competitors }: Compet
{/* USPs */}
<div>
<SectionHeader
label={lang === 'de' ? 'USP — nur ComplAI' : 'USP — ComplAI only'}
label={lang === 'de' ? 'USP — nur COMPLAI' : 'USP — COMPLAI only'}
count={usps.length}
open={openSections.has('usp')}
onToggle={() => toggleSection('usp')}
@@ -705,7 +686,7 @@ export default function CompetitionSlide({ lang, features, competitors }: Compet
<th className="py-2 px-1.5 text-white/40 font-medium text-center">Mid</th>
<th className="py-2 px-1.5 text-white/40 font-medium text-center">Enterprise</th>
<th className="py-2 px-1.5 text-white/40 font-medium text-center">Setup</th>
<th className="py-2 px-1.5 text-white/40 font-medium text-center">{lang === 'de' ? 'Oeffentlich' : 'Public'}</th>
<th className="py-2 px-1.5 text-white/40 font-medium text-center">{lang === 'de' ? 'Öffentlich' : 'Public'}</th>
</tr>
</thead>
<tbody>
@@ -744,39 +725,9 @@ export default function CompetitionSlide({ lang, features, competitors }: Compet
</table>
</div>
{/* Key Insights */}
<GlassCard className="!p-3 mt-4" hover={false}>
<h4 className="text-xs font-semibold text-white/60 mb-2 flex items-center gap-1.5">
<Tag className="w-3.5 h-3.5" />
{lang === 'de' ? 'Pricing-Einordnung' : 'Pricing Context'}
</h4>
<div className="grid grid-cols-2 gap-3 text-[11px]">
<div className="bg-white/[0.03] rounded-lg p-2">
<div className="text-white/50 mb-1">{lang === 'de' ? 'Compliance-Only (DACH)' : 'Compliance Only (DACH)'}</div>
<div className="text-white/80 font-medium">83 499/mo</div>
<div className="text-white/30 text-[10px]">{lang === 'de' ? 'Proliance, heyData — nur DSGVO, kein Code-Security' : 'Proliance, heyData — GDPR only, no code security'}</div>
</div>
<div className="bg-white/[0.03] rounded-lg p-2">
<div className="text-white/50 mb-1">{lang === 'de' ? 'US-Enterprise (Global)' : 'US Enterprise (Global)'}</div>
<div className="text-white/80 font-medium">$500 $7K+/mo</div>
<div className="text-white/30 text-[10px]">{lang === 'de' ? 'Vanta, Drata — SOC 2 Fokus, Setup-Gebuehr, kein Self-Hosted' : 'Vanta, Drata — SOC 2 focus, setup fee, no self-hosted'}</div>
</div>
<div className="bg-indigo-500/5 border border-indigo-500/10 rounded-lg p-2">
<div className="text-indigo-400 mb-1 font-medium">ComplAI</div>
<div className="text-white/80 font-medium">990 2.990/mo</div>
<div className="text-white/30 text-[10px]">{lang === 'de' ? 'Compliance + Code-Security + Self-Hosted KI, kein Setup' : 'Compliance + code security + self-hosted AI, no setup fee'}</div>
</div>
<div className="bg-white/[0.03] rounded-lg p-2">
<div className="text-white/50 mb-1">{lang === 'de' ? 'AppSec-Tools (separat)' : 'AppSec Tools (separate)'}</div>
<div className="text-white/80 font-medium">$10K $500K+/yr</div>
<div className="text-white/30 text-[10px]">{lang === 'de' ? 'Snyk, Veracode — keine Compliance, Cloud-only' : 'Snyk, Veracode — no compliance, cloud only'}</div>
</div>
</div>
</GlassCard>
<p className="text-[10px] text-white/25 text-center mt-3 italic">
{lang === 'de'
? '~ = geschaetzte Preise (nicht oeffentlich). Alle Preise ohne MwSt. Stand: Q1 2026.'
? '~ = geschätzte Preise (nicht öffentlich). Alle Preise ohne MwSt. Stand: Q1 2026.'
: '~ = estimated pricing (not public). All prices excl. VAT. As of Q1 2026.'}
</p>
</FadeInView>
@@ -795,8 +746,8 @@ export default function CompetitionSlide({ lang, features, competitors }: Compet
</span>
<p className="text-white/50 mt-1 leading-relaxed">
{lang === 'de'
? 'Kein Compliance-Anbieter (Vanta, Drata, etc.) bietet DAST, SAST oder LLM-basierte Code-Fixes. Kein AppSec-Anbieter (Snyk, Veracode, etc.) bietet DSGVO/AI-Act-Compliance. ComplAI ist die einzige Plattform, die beides kombiniert.'
: 'No compliance vendor (Vanta, Drata, etc.) offers DAST, SAST, or LLM-based code fixes. No AppSec vendor (Snyk, Veracode, etc.) offers GDPR/AI Act compliance. ComplAI is the only platform combining both.'}
? 'Kein Compliance-Anbieter (Vanta, Drata, etc.) bietet DAST, SAST oder LLM-basierte Code-Fixes. Kein AppSec-Anbieter (Snyk, Veracode, etc.) bietet DSGVO/AI-Act-Compliance. COMPLAI ist die einzige Plattform, die beides kombiniert.'
: 'No compliance vendor (Vanta, Drata, etc.) offers DAST, SAST, or LLM-based code fixes. No AppSec vendor (Snyk, Veracode, etc.) offers GDPR/AI Act compliance. COMPLAI is the only platform combining both.'}
</p>
</div>
</div>
@@ -815,20 +766,46 @@ export default function CompetitionSlide({ lang, features, competitors }: Compet
</div>
</div>
{/* AppSec Feature Matrix */}
<div className="space-y-2">
<div>
<SectionHeader
label={lang === 'de' ? 'USP — nur ComplAI' : 'USP — ComplAI only'}
count={APPSEC_FEATURES.filter(f => f.isUSP).length}
open={openSections.has('appsec-usp')}
onToggle={() => toggleSection('appsec-usp')}
accent="text-indigo-400"
/>
{openSections.has('appsec-usp') && (
<AppSecFeatureTable features={APPSEC_FEATURES.filter(f => f.isUSP)} lang={lang} highlight />
)}
{/* Efficiency Ratios — AppSec */}
<GlassCard className="!p-3 mt-4 mb-4" hover={false}>
<h4 className="text-xs font-semibold text-white/60 mb-2 flex items-center gap-1.5">
<TrendingUp className="w-3.5 h-3.5" />
{lang === 'de' ? 'Effizienz-Kennzahlen' : 'Efficiency Ratios'}
</h4>
<div className="overflow-x-auto">
<table className="w-full text-[11px]">
<thead>
<tr className="border-b border-white/10">
<th className="text-left py-1.5 px-2 text-white/40 font-medium">{lang === 'de' ? 'Kennzahl' : 'Metric'}</th>
{APPSEC_COMPETITORS.map(c => (
<th key={c.name} className="py-1.5 px-2 text-white/50 font-medium text-center">{c.flag} {c.name}</th>
))}
</tr>
</thead>
<tbody>
<tr className="border-b border-white/5">
<td className="py-1.5 px-2 text-white/50">{lang === 'de' ? 'Umsatz / Mitarbeiter' : 'Revenue / Employee'}</td>
{APPSEC_COMPETITORS.map(c => (
<td key={c.name} className="py-1.5 px-2 text-center text-white/70">
${ratio(c.revenueNum, c.employees)}
</td>
))}
</tr>
<tr className="border-b border-white/5">
<td className="py-1.5 px-2 text-white/50">{lang === 'de' ? 'Mitarbeiter' : 'Employees'}</td>
{APPSEC_COMPETITORS.map(c => (
<td key={c.name} className="py-1.5 px-2 text-center text-white/70">
{c.employees.toLocaleString()}
</td>
))}
</tr>
</tbody>
</table>
</div>
</GlassCard>
{/* AppSec Feature Matrix */}
<div className="space-y-2 mt-4">
<div>
<SectionHeader
label={lang === 'de' ? 'Alle AppSec Features' : 'All AppSec Features'}
@@ -926,12 +903,21 @@ function CompetitorCard({ competitor: c, lang }: { competitor: ExtendedCompetito
)
}
const GROUP_LABELS: Record<string, { de: string; en: string; color: string }> = {
'code-security': { de: 'Code Security & DevSecOps', en: 'Code Security & DevSecOps', color: 'text-red-400' },
'ai-data': { de: 'KI & Daten', en: 'AI & Data', color: 'text-purple-400' },
'frameworks': { de: 'Regulatorische Frameworks', en: 'Regulatory Frameworks', color: 'text-blue-400' },
'documentation': { de: 'Compliance-Dokumentation', en: 'Compliance Documentation', color: 'text-emerald-400' },
'operations': { de: 'Operative Compliance', en: 'Operative Compliance', color: 'text-amber-400' },
'platform': { de: 'Technische Plattform', en: 'Technical Platform', color: 'text-cyan-400' },
'industry': { de: 'Branche & Spezial', en: 'Industry & Specialty', color: 'text-orange-400' },
}
function FeatureTable({
features,
lang,
cols,
labels,
highlight,
}: {
features: ComparisonFeature[]
lang: Language
@@ -939,6 +925,41 @@ function FeatureTable({
labels: string[]
highlight?: boolean
}) {
// Build rows with group headers
const rowElements: React.ReactNode[] = []
let lastGroup = ''
features.forEach((f, i) => {
const grp = f.group || ''
if (grp && grp !== lastGroup) {
const gl = GROUP_LABELS[grp]
if (gl) {
rowElements.push(
<tr key={`grp-${grp}`} className="bg-white/[0.02]">
<td colSpan={cols.length + 1} className={`py-1.5 px-2 text-[10px] font-bold uppercase tracking-wider ${gl.color}`}>
{lang === 'de' ? gl.de : gl.en}
</td>
</tr>
)
}
lastGroup = grp
}
rowElements.push(
<tr key={i} className={`border-b border-white/5 ${f.isDiff ? 'bg-indigo-500/5' : ''}`}>
<td className="py-1.5 px-2 flex items-center gap-1.5">
{f.isDiff && <Star className="w-3 h-3 text-yellow-400 shrink-0" />}
<span className={f.isDiff ? 'text-white font-medium' : 'text-white/60'}>
{lang === 'de' ? f.de : f.en}
</span>
</td>
{cols.map(col => (
<td key={col} className="py-1.5 px-1.5 text-center">
<StatusIcon value={f[col as keyof ComparisonFeature] as FeatureStatus} />
</td>
))}
</tr>
)
})
return (
<div className="overflow-x-auto mt-1 mb-1">
<table className="w-full text-[11px]">
@@ -952,23 +973,7 @@ function FeatureTable({
))}
</tr>
</thead>
<tbody>
{features.map((f, i) => (
<tr key={i} className={`border-b border-white/5 ${highlight && f.isDiff ? 'bg-indigo-500/5' : ''}`}>
<td className="py-1.5 px-2 flex items-center gap-1.5">
{f.isDiff && <Star className="w-3 h-3 text-yellow-400 shrink-0" />}
<span className={f.isDiff ? 'text-white font-medium' : 'text-white/60'}>
{lang === 'de' ? f.de : f.en}
</span>
</td>
{cols.map(col => (
<td key={col} className="py-1.5 px-1.5 text-center">
<StatusIcon value={f[col as keyof ComparisonFeature] as FeatureStatus} />
</td>
))}
</tr>
))}
</tbody>
<tbody>{rowElements}</tbody>
</table>
</div>
)
@@ -0,0 +1,103 @@
'use client'
import { Language } from '@/lib/types'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
import { Shield, Lock } from 'lucide-react'
interface DisclaimerSlideProps {
lang: Language
}
export default function DisclaimerSlide({ lang }: DisclaimerSlideProps) {
const de = lang === 'de'
return (
<div className="max-w-4xl mx-auto">
<FadeInView className="text-center mb-6">
<h2 className="text-3xl md:text-4xl font-bold mb-2">
<GradientText>{de ? 'Rechtlicher Hinweis' : 'Legal Notice'}</GradientText>
</h2>
</FadeInView>
<div className="space-y-4 max-h-[70vh] overflow-y-auto pr-2">
{/* Disclaimer */}
<FadeInView delay={0.1}>
<div className="bg-white/[0.03] border border-white/[0.06] rounded-xl p-5">
<div className="flex items-center gap-2 mb-3">
<Shield className="w-4 h-4 text-indigo-400" />
<h3 className="text-sm font-bold text-indigo-400 uppercase tracking-wider">
{de ? 'Haftungsausschluss' : 'Disclaimer'}
</h3>
</div>
<div className="space-y-3 text-xs text-white/40 leading-relaxed">
<p>
{de
? 'Dieses Dokument wird vorgelegt von Benjamin Boenisch, wohnhaft in Bodman, Deutschland, und Sharang Parnerkar, wohnhaft in Engen, Deutschland (nachfolgend „Gründer"). Die Gründer beabsichtigen die Gründung der BreakPilot GmbH im dritten Quartal 2026. Zum Zeitpunkt der Erstellung dieses Dokuments ist die Gesellschaft weder gegründet noch im Handelsregister eingetragen. Die Gründer handeln ausschließlich als Privatpersonen im Rahmen der Gründungsvorbereitung.'
: 'This document is presented by Benjamin Boenisch, residing in Bodman, Germany, and Sharang Parnerkar, residing in Engen, Germany (hereinafter "Founders"). The Founders intend to establish BreakPilot GmbH in Q3 2026. At the time of this document, the company is neither founded nor registered in the commercial register. The Founders act exclusively as private individuals in preparation of the founding.'}
</p>
<p>
{de
? 'Dieses Dokument stellt weder ein Angebot zum Verkauf noch eine Aufforderung zur Abgabe eines Angebots zum Erwerb von Wertpapieren, Gesellschaftsanteilen oder sonstigen Vermögensanlagen dar. Es handelt sich nicht um einen Wertpapierprospekt im Sinne des VermAnlG oder der EU-Prospektverordnung. Jede etwaige künftige Beteiligung begründet sich ausschließlich auf gesonderten, rechtlich geprüften Beteiligungsverträgen.'
: 'This document constitutes neither an offer to sell nor a solicitation of an offer to acquire securities, company shares or other financial instruments. It is not a securities prospectus within the meaning of the VermAnlG or the EU Prospectus Regulation. Any future participation shall be based exclusively on separate, legally reviewed participation agreements.'}
</p>
<p>
{de
? 'Dieses Dokument enthält zukunftsgerichtete Aussagen, die auf gegenwärtigen Erwartungen und Annahmen beruhen. Solche Aussagen beinhalten Risiken und Ungewissheiten, die dazu führen können, dass tatsächliche Ergebnisse wesentlich von den dargestellten Erwartungen abweichen. Sämtliche Finanzangaben in dieser Präsentation sind Planzahlen und stellen keine Garantie für künftige Ergebnisse dar.'
: 'This document contains forward-looking statements based on current expectations and assumptions. Such statements involve risks and uncertainties that may cause actual results to differ materially from stated expectations. All financial figures in this presentation are projections and do not constitute a guarantee of future results.'}
</p>
<p>
{de
? 'Sämtliche Angaben wurden mit Sorgfalt zusammengestellt, erheben jedoch keinen Anspruch auf Vollständigkeit oder Richtigkeit. Die Gründer übernehmen keine Haftung für die Aktualität, Korrektheit oder Vollständigkeit der Informationen, sofern kein vorsätzliches oder grob fahrlässiges Verschulden vorliegt. Eine Beteiligung an einem jungen Unternehmen ist mit erheblichen Risiken verbunden, einschließlich des Risikos eines Totalverlusts des eingesetzten Kapitals.'
: 'All information has been compiled with care but makes no claim to completeness or accuracy. The Founders assume no liability for the timeliness, correctness or completeness of the information, unless there is intentional or grossly negligent fault. An investment in a young company involves significant risks, including the risk of total loss of invested capital.'}
</p>
</div>
</div>
</FadeInView>
{/* Confidentiality */}
<FadeInView delay={0.2}>
<div className="bg-white/[0.03] border border-white/[0.06] rounded-xl p-5">
<div className="flex items-center gap-2 mb-3">
<Lock className="w-4 h-4 text-purple-400" />
<h3 className="text-sm font-bold text-purple-400 uppercase tracking-wider">
{de ? 'Vertraulichkeit' : 'Confidentiality'}
</h3>
</div>
<div className="space-y-3 text-xs text-white/40 leading-relaxed">
<p>
{de
? 'Dieses Dokument ist vertraulich und wurde ausschließlich für den namentlich eingeladenen Empfänger erstellt. Durch die Kenntnisnahme erklärt sich der Empfänger mit folgenden Bedingungen einverstanden:'
: 'This document is confidential and has been prepared exclusively for the personally invited recipient. By accessing this document, the recipient agrees to the following terms:'}
</p>
<p>
{de
? '(a) Geheimhaltung — Der Empfänger verpflichtet sich, den Inhalt vertraulich zu behandeln und nicht an Dritte weiterzugeben, zu kopieren oder zugänglich zu machen. Ausgenommen sind Berater (Rechtsanwälte, Steuerberater), die berufsrechtlich zur Verschwiegenheit verpflichtet sind.'
: '(a) Confidentiality — The recipient undertakes to treat the content confidentially and not to disclose, copy or make it accessible to third parties. Excluded are advisors (lawyers, tax advisors) who are professionally bound to secrecy.'}
</p>
<p>
{de
? '(b) Zweckbindung — Die Informationen dürfen ausschließlich zur Bewertung einer möglichen Beteiligung verwendet werden. Jede anderweitige Nutzung ist untersagt.'
: '(b) Purpose limitation — The information may only be used for the purpose of evaluating a possible participation. Any other use is prohibited.'}
</p>
<p>
{de
? '(c) Geltungsdauer — Diese Vertraulichkeitsverpflichtung gilt für drei (3) Jahre ab Übermittlung, unabhängig davon, ob eine Beteiligung zustande kommt. Es gilt deutsches Recht. Gerichtsstand ist Konstanz, Deutschland.'
: '(c) Duration — This confidentiality obligation applies for three (3) years from transmission, regardless of whether a participation materializes. German law applies. Place of jurisdiction is Konstanz, Germany.'}
</p>
</div>
</div>
</FadeInView>
<FadeInView delay={0.3}>
<p className="text-center text-[10px] text-white/20">
{de ? 'Stand: April 2026 · Dieser Hinweis ersetzt keine Rechtsberatung.' : 'As of: April 2026 · This notice does not replace legal advice.'}
</p>
</FadeInView>
</div>
</div>
)
}
@@ -30,33 +30,26 @@ export default function EngineeringSlide({ lang }: EngineeringSlideProps) {
const heroStats = [
{
value: '481K',
value: '500K+',
label: de ? 'Zeilen Code' : 'Lines of Code',
sub: 'Go · Python · TypeScript',
color: 'text-indigo-400',
borderColor: 'border-indigo-500/30',
},
{
value: '10',
label: de ? 'Docker Container' : 'Docker Containers',
sub: de ? 'Coolify → Hetzner (amd64)' : 'Coolify → Hetzner (amd64)',
value: '385',
label: de ? 'Dokumente im RAG' : 'Documents in RAG',
sub: de ? 'EU · DACH · Frameworks · Urteile' : 'EU · DACH · Frameworks · Rulings',
color: 'text-emerald-400',
borderColor: 'border-emerald-500/30',
},
{
value: '48+',
label: de ? 'SDK-Module' : 'SDK Modules',
sub: de ? 'DSGVO · AI Act · NIS2 · CRA' : 'GDPR · AI Act · NIS2 · CRA',
value: '25K+',
label: de ? 'Compliance Controls' : 'Compliance Controls',
sub: de ? '6 Pipeline-Versionen' : '6 pipeline versions',
color: 'text-purple-400',
borderColor: 'border-purple-500/30',
},
{
value: '14',
label: 'Dockerfiles',
sub: de ? 'Vollstaendig containerisiert' : 'Fully containerized',
color: 'text-amber-400',
borderColor: 'border-amber-500/30',
},
]
const languageBreakdown = [
@@ -69,17 +62,17 @@ export default function EngineeringSlide({ lang }: EngineeringSlideProps) {
{
icon: GitBranch,
label: 'Gitea + Actions',
desc: de ? 'Self-hosted Git + CI/CD · Lint → Tests → Validierung' : 'Self-hosted Git + CI/CD · Lint → Tests → Validation',
desc: de ? 'Self-hosted Git + CI/CD · Lint → Tests → Image-Build' : 'Self-hosted Git + CI/CD · Lint → Tests → Image build',
},
{
icon: Workflow,
label: 'Coolify',
desc: de ? 'Auto-Deploy bei Push · Docker Compose auf Hetzner · Health Checks' : 'Auto-deploy on push · Docker Compose on Hetzner · Health checks',
label: 'orca',
desc: de ? 'Single-Binary Orchestrator (Rust) · Webhook-Deploy · Auto-TLS · Raft' : 'Single-binary orchestrator (Rust) · Webhook deploys · Auto-TLS · Raft',
},
{
icon: Container,
label: 'Docker Compose',
desc: de ? 'arm64 → amd64 Build-Pipeline · Multi-Stage Builds' : 'arm64 → amd64 build pipeline · Multi-stage builds',
label: 'Private Registry',
desc: de ? 'registry.meghsakha.com · Signed Images · Tag pro Commit (:SHA + :latest)' : 'registry.meghsakha.com · Signed images · Per-commit tags (:SHA + :latest)',
},
{
icon: ShieldCheck,
@@ -88,13 +81,13 @@ export default function EngineeringSlide({ lang }: EngineeringSlideProps) {
},
{
icon: Database,
label: 'HashiCorp Vault',
desc: de ? 'Secrets Management · Auto-Rotation · PKI' : 'Secrets Management · Auto-Rotation · PKI',
label: 'Infisical',
desc: de ? 'Secrets Management · Rotation · RBAC · End-to-End verschlüsselt' : 'Secrets Management · Rotation · RBAC · End-to-end encrypted',
},
{
icon: Server,
label: de ? 'EU-Cloud Infrastruktur' : 'EU Cloud Infrastructure',
desc: de ? 'Hetzner · SysEleven (BSI) · OVH · PostgreSQL · Qdrant' : 'Hetzner · SysEleven (BSI) · OVH · PostgreSQL · Qdrant',
desc: de ? 'Hetzner · SysEleven (BSI) · PostgreSQL · Qdrant' : 'Hetzner · SysEleven (BSI) · PostgreSQL · Qdrant',
},
]
@@ -120,8 +113,8 @@ export default function EngineeringSlide({ lang }: EngineeringSlideProps) {
color: 'text-emerald-400',
dotColor: 'bg-emerald-400',
services: de
? ['PostgreSQL 17 (Hetzner)', 'Qdrant Vector DB', 'DSMS/IPFS Node + Gateway', 'MinIO Object Storage']
: ['PostgreSQL 17 (Hetzner)', 'Qdrant Vector DB', 'DSMS/IPFS Node + Gateway', 'MinIO Object Storage'],
? ['orca (Rust) Orchestrator', 'Infisical Secrets', 'PostgreSQL 17 (Hetzner)', 'Qdrant Vector DB', 'DSMS/IPFS Node + Gateway', 'Private Registry']
: ['orca (Rust) Orchestrator', 'Infisical Secrets', 'PostgreSQL 17 (Hetzner)', 'Qdrant Vector DB', 'DSMS/IPFS Node + Gateway', 'Private Registry'],
},
]
@@ -139,7 +132,7 @@ export default function EngineeringSlide({ lang }: EngineeringSlideProps) {
{/* Hero Stats */}
<FadeInView delay={0.1}>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-5">
<div className="grid grid-cols-3 gap-3 mb-5">
{heroStats.map((stat, idx) => (
<div
key={idx}
@@ -261,8 +254,8 @@ export default function EngineeringSlide({ lang }: EngineeringSlideProps) {
<div className="mt-3 pt-3 border-t border-white/5">
<p className="text-[10px] text-white/20 text-center">
{de
? '100% EU-Cloud · Hetzner + SysEleven (BSI) + OVH · Keine US-Anbieter · Volle Datenkontrolle'
: '100% EU Cloud · Hetzner + SysEleven (BSI) + OVH · No US Providers · Full Data Control'}
? '100% EU-Cloud · Hetzner + SysEleven (BSI) · Keine US-Anbieter · Volle Datenkontrolle'
: '100% EU Cloud · Hetzner + SysEleven (BSI) · No US Providers · Full Data Control'}
</p>
</div>
</GlassCard>
@@ -1,8 +1,9 @@
'use client'
import { useCallback } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { Language, PitchData } from '@/lib/types'
import { t, formatEur } from '@/lib/i18n'
import { useFpKPIs } from '@/lib/hooks/useFpKPIs'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
import GlassCard from '../ui/GlassCard'
@@ -11,13 +12,25 @@ import { Download, Shield, Server, Brain, TrendingUp, FileText, Target, ScanLine
interface ExecutiveSummarySlideProps {
lang: Language
data: PitchData
investorId?: string | null
preferredScenarioId?: string | null
isWandeldarlehen?: boolean
}
export default function ExecutiveSummarySlide({ lang, data }: ExecutiveSummarySlideProps) {
export default function ExecutiveSummarySlide({ lang, data, investorId, preferredScenarioId, isWandeldarlehen }: ExecutiveSummarySlideProps) {
const i = t(lang)
const es = i.executiveSummary
const de = lang === 'de'
// Unternehmensentwicklung from fp_* tables (source of truth)
const { kpis: fpKPIs } = useFpKPIs(isWandeldarlehen)
// Pipeline stats from DB
const [pipelineStats, setPipelineStats] = useState<Record<string, { value: number }>>({})
useEffect(() => {
fetch('/api/pipeline-stats', { cache: 'no-store' }).then(r => r.json()).then(setPipelineStats).catch(() => {})
}, [])
const funding = data.funding
const amount = funding?.amount_eur || 0
const amountLabel = amount >= 1_000_000
@@ -138,7 +151,7 @@ export default function ExecutiveSummarySlide({ lang, data }: ExecutiveSummarySl
<div class="header">
<div>
<h1>BreakPilot COMPL<span style="color:#4f46e5;">AI</span></h1>
<div class="tagline">Onepager</div>
<div class="tagline">Executive Summary</div>
</div>
<div class="badge">Pre-Seed ${funding?.target_date ? 'Q' + Math.ceil((new Date(funding.target_date).getMonth() + 1) / 3) + ' ' + new Date(funding.target_date).getFullYear() : 'Q4 2026'}</div>
</div>
@@ -165,7 +178,7 @@ export default function ExecutiveSummarySlide({ lang, data }: ExecutiveSummarySl
<div style="display:grid;grid-template-columns:repeat(5,1fr);gap:8px;margin-bottom:10px;">
<div class="kpi"><div class="value">25.000+</div><div class="label">${es.controls}</div></div>
<div class="kpi"><div class="value">110</div><div class="label">${es.regulations}</div></div>
<div class="kpi"><div class="value">380+</div><div class="label">${es.regulations}</div></div>
<div class="kpi"><div class="value">10</div><div class="label">${es.industries}</div></div>
<div class="kpi"><div class="value">500K+</div><div class="label">${es.linesOfCode}</div></div>
<div class="kpi"><div class="value">${amountLabel}</div><div class="label">${es.theAsk}</div></div>
@@ -179,7 +192,7 @@ export default function ExecutiveSummarySlide({ lang, data }: ExecutiveSummarySl
<li><strong>SAST + DAST + SBOM</strong> ${de ? '\\u2014 Vollumf\\u00e4ngliche Sicherheitstests bei jeder Code-\\u00c4nderung' : '\\u2014 Full security testing on every code change'}</li>
<li><strong>${de ? 'KI-gest\\u00fctztes Pentesting' : 'AI-powered Pentesting'}</strong> ${de ? '\\u2014 Kontinuierlich statt einmal im Jahr' : '\\u2014 Continuous instead of once a year'}</li>
<li><strong>CE-Software-Risikobeurteilung</strong> ${de ? '\\u2014 F\\u00fcr Maschinenverordnung und Produktsicherheit' : '\\u2014 For Machinery Regulation and product safety'}</li>
<li><strong>Jira-Integration</strong> ${de ? '\\u2014 Findings als Tickets mit Implementierungsvorschl\\u00e4gen' : '\\u2014 Findings as tickets with implementation suggestions'}</li>
<li><strong>Issue-Tracker-Integration</strong> ${de ? '\\u2014 Findings als Tickets mit Implementierungsvorschl\\u00e4gen' : '\\u2014 Findings as tickets with implementation suggestions'}</li>
<li><strong>Audit-Trail</strong> ${de ? '\\u2014 L\\u00fcckenloser Nachweis von Erkennung bis Behebung' : '\\u2014 Complete evidence from detection to remediation'}</li>
</ul>
</div>
@@ -191,7 +204,7 @@ export default function ExecutiveSummarySlide({ lang, data }: ExecutiveSummarySl
<li><strong>Audit Manager</strong> ${de ? '\\u2014 Abweichungen End-to-End: Rollen, Stichtage, Eskalation' : '\\u2014 Deviations end-to-end: roles, deadlines, escalation'}</li>
<li><strong>Compliance LLM</strong> ${de ? '\\u2014 GPT f\\u00fcr Text und Audio, sicher in der EU gehostet' : '\\u2014 GPT for text and audio, securely hosted in EU'}</li>
<li><strong>Academy</strong> ${de ? '\\u2014 Online-Schulungen f\\u00fcr GF und Mitarbeiter' : '\\u2014 Online training for management and employees'}</li>
<li><strong>${de ? 'BSI-Cloud DE / OVH FR' : 'BSI Cloud DE / OVH FR'}</strong> ${de ? '\\u2014 Keine US-SaaS, Jitsi, Matrix, volle Integration' : '\\u2014 No US SaaS, Jitsi, Matrix, full integration'}</li>
<li><strong>${de ? 'BSI-Cloud DE / FR' : 'BSI Cloud DE / FR'}</strong> ${de ? '\\u2014 Keine US-SaaS, Jitsi, Matrix, volle Integration' : '\\u2014 No US SaaS, Jitsi, Matrix, full integration'}</li>
</ul>
</div>
</div>
@@ -208,9 +221,9 @@ export default function ExecutiveSummarySlide({ lang, data }: ExecutiveSummarySl
<div class="card bottom-card">
<div class="section-title">${de ? 'Gesch\\u00e4ftsmodell' : 'Business Model'}</div>
<ul>
<li><strong>SaaS Cloud</strong> ${de ? '\\u2014 BSI DE / OVH FR, mitarbeiterbasiert' : '\\u2014 BSI DE / OVH FR, employee-based'}</li>
<li><strong>SaaS Cloud</strong> ${de ? '\\u2014 BSI DE / FR, mitarbeiterbasiert' : '\\u2014 BSI DE / FR, employee-based'}</li>
<li><strong>${de ? 'Modular w\\u00e4hlbar' : 'Modular choice'}</strong> ${de ? '\\u2014 Einzelne Module oder Full Compliance' : '\\u2014 Single modules or full compliance'}</li>
<li><strong>${de ? 'ROI ab Tag 1' : 'ROI from day 1'}</strong> ${de ? '\\u2014 Kunde spart 50.000+ EUR/Jahr' : '\\u2014 Customer saves EUR 50,000+/year'}</li>
<li><strong>${de ? 'ROI ab Tag 1' : 'ROI from day 1'}</strong> ${de ? '\\u2014 KMU spart 55.000 EUR/Jahr (3,7x ROI)' : '\\u2014 SME saves EUR 55,000/year (3.7x ROI)'}</li>
</ul>
</div>
<div class="card bottom-card">
@@ -263,7 +276,7 @@ export default function ExecutiveSummarySlide({ lang, data }: ExecutiveSummarySl
<h2 className="text-3xl md:text-5xl font-bold mb-2">
<span className="text-white">BreakPilot COMPL</span><GradientText>AI</GradientText>
</h2>
<p className="text-base text-white/50 max-w-2xl mx-auto">Onepager</p>
<p className="text-base text-white/50 max-w-2xl mx-auto">Executive Summary</p>
</FadeInView>
{/* Hero Description */}
@@ -278,17 +291,28 @@ export default function ExecutiveSummarySlide({ lang, data }: ExecutiveSummarySl
</div>
</FadeInView>
{/* USP Banner */}
{/* USP / MOAT */}
<FadeInView delay={0.1} className="mb-4">
<div className="bg-gradient-to-r from-indigo-500/20 to-purple-500/20 border border-indigo-500/30 rounded-2xl px-5 py-3 text-center">
<span className="text-base font-bold text-indigo-400 uppercase tracking-wider">{es.usp}</span>
<p className="text-sm text-white/80 mt-2 leading-relaxed">
{de
? 'Die einzige Plattform, die kontinuierliche Code-Security, automatisierte Compliance-Dokumentation und CE-konforme Software-Risikobeurteilung in einem System vereint vollständig betrieben auf europäischer Infrastruktur (Deutschland oder Frankreich).'
: 'The only platform combining continuous code security, automated compliance documentation and CE-compliant software risk assessment in one system fully operated on European infrastructure (Germany or France).'
}
</p>
<p className="text-sm font-semibold text-indigo-300 mt-1.5">{de ? '100\u00a0% Datensouveränität ohne Abhängigkeit von US-Anbietern.' : '100% data sovereignty without dependence on US providers.'}</p>
<div className="bg-gradient-to-r from-indigo-500/10 to-purple-500/10 border border-indigo-500/20 rounded-2xl px-5 py-4">
<h3 className="text-sm font-bold text-indigo-400 uppercase tracking-wider text-center">{de ? 'Unser MOAT' : 'Our MOAT'}</h3>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mt-3">
<div className="text-center">
<p className="text-sm font-bold text-indigo-300">Traceability</p>
<p className="text-xs text-white/50">{de ? 'Gesetz → Control → Code' : 'Law → Control → Code'}</p>
</div>
<div className="text-center">
<p className="text-sm font-bold text-purple-300">Continuous Engine</p>
<p className="text-xs text-white/50">{de ? 'Echtzeit bei jeder Änderung' : 'Real-time on every change'}</p>
</div>
<div className="text-center">
<p className="text-sm font-bold text-amber-300">Compliance Optimizer</p>
<p className="text-xs text-white/50">{de ? 'Maximale KI-Nutzung im Rahmen' : 'Max AI use within regulations'}</p>
</div>
<div className="text-center">
<p className="text-sm font-bold text-emerald-300">EU-Trust Stack</p>
<p className="text-xs text-white/50">{de ? '100% EU, kein US-SaaS' : '100% EU, no US SaaS'}</p>
</div>
</div>
</div>
</FadeInView>
@@ -364,8 +388,8 @@ export default function ExecutiveSummarySlide({ lang, data }: ExecutiveSummarySl
<FadeInView delay={0.25} className="mb-4">
<div className="grid grid-cols-3 md:grid-cols-6 gap-2">
{[
{ value: '25.000+', label: es.controls, icon: Shield, color: '#6366f1' },
{ value: '110', label: es.regulations, icon: Brain, color: '#60a5fa' },
{ value: pipelineStats.unique_controls ? `${Math.round(pipelineStats.unique_controls.value / 1000)}k+` : '25k+', label: es.controls, icon: Shield, color: '#6366f1' },
{ value: pipelineStats.legal_sources ? `${pipelineStats.legal_sources.value}+` : '380+', label: es.regulations, icon: Brain, color: '#60a5fa' },
{ value: '10', label: es.industries, icon: Target, color: '#34d399' },
{ value: '500K+', label: es.linesOfCode, icon: Cpu, color: '#fbbf24' },
{ value: '80%', label: de ? 'Zeitersparnis bei\nCompliance-Prüfungen' : 'Time saved on\ncompliance checks', icon: TrendingUp, color: '#10b981' },
@@ -393,7 +417,7 @@ export default function ExecutiveSummarySlide({ lang, data }: ExecutiveSummarySl
de ? 'SAST + DAST + SBOM — bei jeder Code-Änderung' : 'SAST + DAST + SBOM — on every code change',
de ? 'KI-gestütztes Pentesting — kontinuierlich statt jährlich' : 'AI-powered pentesting — continuous not annual',
de ? 'CE-Software-Risikobeurteilung für Maschinenverordnung' : 'CE software risk assessment for Machinery Regulation',
de ? 'Integration in Kundenprozesse — Tickets mit Implementierungsvorschlägen' : 'Integration into customer processes — tickets with implementation suggestions',
de ? 'Compliance Optimizer + Tender Matching gegen Codebase' : 'Compliance Optimizer + Tender Matching against codebase',
de ? 'Lückenloser Audit-Trail von Erkennung bis Behebung' : 'Complete audit trail from detection to remediation',
].map((item, idx) => (
<p key={idx} className="text-xs text-white/60 pl-3 relative">
@@ -416,7 +440,7 @@ export default function ExecutiveSummarySlide({ lang, data }: ExecutiveSummarySl
de ? 'Audit Manager — Abweichungen End-to-End mit Eskalation' : 'Audit Manager — deviations end-to-end with escalation',
de ? 'Compliance LLM — GPT für Text und Audio, EU-gehostet' : 'Compliance LLM — GPT for text and audio, EU-hosted',
de ? 'Academy — Online-Schulungen für GF und Mitarbeiter' : 'Academy — online training for management and employees',
de ? 'BSI-Cloud DE / OVH FR' : 'BSI Cloud DE / OVH FR',
de ? 'BSI-Cloud DE / FR' : 'BSI Cloud DE / FR',
].map((item, idx) => (
<p key={idx} className="text-xs text-white/60 pl-3 relative">
<span className="absolute left-0 top-1 w-1.5 h-1.5 rounded-full bg-cyan-400/60" />
@@ -464,9 +488,9 @@ export default function ExecutiveSummarySlide({ lang, data }: ExecutiveSummarySl
{ name: 'Consent', desc: de ? 'Einwilligungen' : 'Consent mgmt', color: '#14b8a6', icon: UserCheck },
{ name: de ? 'Notfallpläne' : 'Incident Resp.', desc: de ? 'Vorfälle, Meldung' : 'Breaches, reporting', color: '#f59e0b', icon: AlertTriangle },
{ name: 'Compliance LLM', desc: de ? 'GPT Text + Audio' : 'GPT text + audio', color: '#a855f7', icon: Brain },
{ name: 'Cookie-Generator', desc: de ? 'Cookie-Banner' : 'Cookie banner', color: '#8b5cf6', icon: Shield },
{ name: 'Tender Matching', desc: de ? 'RFQ gegen Codebase' : 'RFQ vs codebase', color: '#8b5cf6', icon: Shield },
{ name: 'Academy', desc: de ? 'Schulungen' : 'Training', color: '#ec4899', icon: GraduationCap },
{ name: de ? 'Integration' : 'Integration', desc: de ? 'Ticketsysteme' : 'Ticket systems', color: '#0ea5e9', icon: Cpu },
{ name: 'Compliance Optimizer', desc: de ? 'Maximale KI-Nutzung im Rahmen' : 'Max AI usage within limits', color: '#0ea5e9', icon: Cpu },
{ name: de ? 'Kommunikation' : 'Communication', desc: de ? 'Chat + Video + AI' : 'Chat + video + AI', color: '#22c55e', icon: Server },
].map((mod, idx) => {
const Icon = mod.icon
@@ -527,45 +551,47 @@ export default function ExecutiveSummarySlide({ lang, data }: ExecutiveSummarySl
<span>{de ? 'Jahr' : 'Year'}</span><span className="text-right">MA</span><span className="text-right">{de ? 'Kunden' : 'Customers'}</span><span className="text-right">ARR</span>
</div>
<div className="space-y-1">
{[
{ year: '2026', emp: '5', cust: '~17', arr: de ? '~84k EUR' : '~EUR 84k' },
{ year: '2027', emp: '10', cust: '~132', arr: de ? '~1,1 Mio. EUR' : '~EUR 1.1M' },
{ year: '2028', emp: '17', cust: '~400', arr: de ? '~3,6 Mio. EUR' : '~EUR 3.6M' },
{ year: '2029', emp: '25', cust: '~780', arr: de ? '~6,9 Mio. EUR' : '~EUR 6.9M' },
{ year: '2030', emp: '35', cust: '~1.320', arr: de ? '~11,1 Mio. EUR' : '~EUR 11.1M' },
].map((r, idx) => (
{!fpKPIs.y2026 ? (
<p className="text-xs text-white/30 text-center py-2">{de ? 'Lade Finanzplan...' : 'Loading financial plan...'}</p>
) : [2026, 2027, 2028, 2029, 2030].map((year, idx) => {
const k = fpKPIs[`y${year}`]
if (!k) return null
const arrLabel = k.arr >= 1_000_000
? (de ? `~${(k.arr / 1_000_000).toFixed(1).replace('.', ',')} Mio. EUR` : `~EUR ${(k.arr / 1_000_000).toFixed(1)}M`)
: (de ? `~${Math.round(k.arr / 1000)}k EUR` : `~EUR ${Math.round(k.arr / 1000)}k`)
return (
<div key={idx} className="grid grid-cols-4 gap-x-3 text-xs">
<span className="text-white/40">{r.year}</span>
<span className="text-right text-white/50">{r.emp}</span>
<span className="text-right text-white/50">{r.cust}</span>
<span className={`text-right font-mono ${idx >= 3 ? 'text-emerald-300 font-bold' : 'text-white/70'}`}>{r.arr}</span>
<span className="text-white/40">{year}</span>
<span className="text-right text-white/50">{k.headcount}</span>
<span className="text-right text-white/50">~{k.customers.toLocaleString('de-DE')}</span>
<span className={`text-right font-mono ${idx >= 3 ? 'text-emerald-300 font-bold' : 'text-white/70'}`}>{arrLabel}</span>
</div>
))}
)
})}
</div>
</GlassCard>
<GlassCard delay={0.55} hover={false} className="p-3 flex-1">
<h3 className="text-xs font-bold text-red-400 uppercase tracking-wider mb-2">{de ? 'Wettbewerber' : 'Competitors'}</h3>
<div className="grid grid-cols-6 gap-x-2 text-[8px] text-white/30 uppercase tracking-wider mb-1.5 border-b border-white/10 pb-1">
<span></span><span>{de ? 'Gegr.' : 'Est.'}</span><span>MA</span><span className="text-right">{de ? 'Kunden' : 'Cust.'}</span><span className="text-right">ARR</span><span className="text-right">Invest</span>
<div className="grid grid-cols-5 gap-x-3 text-[9px] text-white/30 uppercase tracking-wider mb-1.5 border-b border-white/10 pb-1">
<span></span><span>{de ? 'Gegr.' : 'Est.'}</span><span>MA</span><span className="text-right">{de ? 'Kunden' : 'Cust.'}</span><span className="text-right">ARR</span>
</div>
<div className="space-y-1">
<div className="space-y-1.5">
{[
{ name: 'Vanta', flag: '🇺🇸', year: '2018', emp: '500+', cust: '8.000+', rev: '$220M', invest: '$504M' },
{ name: 'Drata', flag: '🇺🇸', year: '2020', emp: '500+', cust: '5.000+', rev: '$100M', invest: '$328M' },
{ name: 'Sprinto', flag: '🇮🇳', year: '2020', emp: '345', cust: '2.000+', rev: '$38M', invest: '$32M' },
{ name: 'Delve', flag: '🇺🇸', year: '2024', emp: '24', cust: '—', rev: '$2,6M', invest: '$35M' },
{ name: 'DataGuard', flag: '🇩🇪', year: '2017', emp: '400+', cust: '4.000+', rev: '€20-30M', invest: '€65M' },
{ name: 'Proliance', flag: '🇩🇪', year: '2017', emp: '100+', cust: '2.500+', rev: '€5-10M', invest: 'k.A.' },
{ name: 'heyData', flag: '🇩🇪', year: '2019', emp: '80+', cust: '2.000+', rev: '€3-10M', invest: '€18M' },
{ name: 'Vanta', flag: '🇺🇸', year: '2018', emp: '500+', cust: '8.000+', rev: '$220M' },
{ name: 'Drata', flag: '🇺🇸', year: '2020', emp: '500+', cust: '5.000+', rev: '$100M' },
{ name: 'Sprinto', flag: '🇮🇳', year: '2020', emp: '345', cust: '2.000+', rev: '$38M' },
{ name: 'Delve', flag: '🇺🇸', year: '2024', emp: '24', cust: '—', rev: '$2,6M' },
{ name: 'DataGuard', flag: '🇩🇪', year: '2017', emp: '400+', cust: '4.000+', rev: '€20-30M' },
{ name: 'Proliance', flag: '🇩🇪', year: '2017', emp: '100+', cust: '2.500+', rev: '€5-10M' },
{ name: 'heyData', flag: '🇩🇪', year: '2019', emp: '80+', cust: '2.000+', rev: '€3-10M' },
].map((c, idx) => (
<div key={idx} className="grid grid-cols-6 gap-x-2 text-[9px]">
<span className="text-white/70">{c.flag} {c.name}</span>
<span className="text-white/30">{c.year}</span>
<span className="text-white/40">{c.emp}</span>
<span className="text-right text-white/50">{c.cust}</span>
<span className="text-right text-white/50">{c.rev}</span>
<span className="text-right text-white/60 font-mono">{c.invest}</span>
<div key={idx} className="grid grid-cols-5 gap-x-3 text-[10px]">
<span className="text-white/70 font-medium">{c.flag} {c.name}</span>
<span className="text-white/40">{c.year}</span>
<span className="text-white/50">{c.emp}</span>
<span className="text-right text-white/60">{c.cust}</span>
<span className="text-right text-white/70 font-mono font-semibold">{c.rev}</span>
</div>
))}
</div>
@@ -578,10 +604,9 @@ export default function ExecutiveSummarySlide({ lang, data }: ExecutiveSummarySl
<h3 className="text-xs font-bold text-amber-400 uppercase tracking-wider mb-2">{de ? 'Pricing' : 'Pricing'}</h3>
<div className="space-y-1.5">
{[
{ tier: 'Startup', price: de ? 'ab 3.600€/J.' : 'from €3,600/yr' },
{ tier: '1050 MA', price: de ? 'ab 15.000€/J.' : 'from €15k/yr' },
{ tier: '50250 MA', price: de ? 'ab 30.000€/J.' : 'from €30k/yr' },
{ tier: '250+ MA', price: de ? 'ab 40.000€/J.' : 'from €40k/yr', highlight: true },
{ tier: de ? 'Starter (<10 MA)' : 'Starter (<10 emp.)', price: de ? '3.600€/J.' : '€3,600/yr' },
{ tier: de ? 'Professional (10250)' : 'Professional (10250)', price: de ? '15.00040.000€/J.' : '€15k40k/yr', highlight: true },
{ tier: de ? 'Enterprise (250+)' : 'Enterprise (250+)', price: de ? 'ab 50.000€/J.' : 'from €50k/yr' },
].map((t, idx) => (
<div key={idx} className={`flex justify-between text-xs ${t.highlight ? 'text-amber-300 font-bold' : 'text-white/60'}`}>
<span>{t.tier}</span>
@@ -593,11 +618,12 @@ export default function ExecutiveSummarySlide({ lang, data }: ExecutiveSummarySl
<GlassCard delay={0.65} hover={false} className="p-3">
<h3 className="text-xs font-bold text-emerald-400 uppercase tracking-wider mb-2">{de ? 'Kundenersparnis' : 'Customer Savings'}</h3>
<div className="space-y-1.5 text-xs text-white/60">
<div className="flex justify-between"><span>Pentests</span><strong className="text-emerald-300">30k</strong></div>
<div className="flex justify-between"><span>CE-Beurt.</span><strong className="text-emerald-300">20k</strong></div>
<div className="flex justify-between"><span>Audit Mgr.</span><strong className="text-emerald-300">60k+</strong></div>
<div className="flex justify-between border-t border-white/10 pt-1 mt-1"><span className="font-bold text-white/80">{de ? 'pro Jahr' : '/year'}</span><strong className="text-emerald-300">50-110k</strong></div>
<div className="space-y-1 text-xs text-white/60">
<div className="flex justify-between"><span>Pentests</span><strong className="text-emerald-300">13k</strong></div>
<div className="flex justify-between"><span>CE-Risiko</span><strong className="text-emerald-300">9k</strong></div>
<div className="flex justify-between"><span>{de ? 'Compliance-Zeit' : 'Compliance time'}</span><strong className="text-emerald-300">15k</strong></div>
<div className="flex justify-between"><span>{de ? 'Audit-Vorb.' : 'Audit prep.'}</span><strong className="text-emerald-300">9k</strong></div>
<div className="flex justify-between border-t border-white/10 pt-1 mt-1"><span className="font-bold text-white/80">{de ? 'KMU/Jahr' : 'SME/year'}</span><strong className="text-emerald-300">55k</strong></div>
</div>
</GlassCard>
</div>
@@ -607,10 +633,10 @@ export default function ExecutiveSummarySlide({ lang, data }: ExecutiveSummarySl
{/* Disclaimer */}
<FadeInView delay={0.7} className="mb-4">
<div className="bg-white/[0.03] border border-white/[0.03] rounded-lg px-4 py-3">
<h4 className="text-[10px] font-bold text-white/30 uppercase tracking-wider mb-1">{de ? 'Hinweis / Haftungsausschluss' : 'Disclaimer'}</h4>
<p className="text-[9px] text-white/20 leading-relaxed">
<h4 className="text-xs font-bold text-white/40 uppercase tracking-wider mb-1">{de ? 'Hinweis / Haftungsausschluss' : 'Disclaimer'}</h4>
<p className="text-[11px] text-white/30 leading-relaxed">
{de
? 'Dieses Dokument dient ausschliesslich Informationszwecken und stellt weder ein Angebot zum Verkauf noch eine Aufforderung zum Kauf von Anteilen oder Wertpapieren dar. Die enthaltenen Informationen wurden vom Team Breakpilot (Gruenderteam, noch keine Gesellschaft gegruendet) nach bestem Wissen und Gewissen erstellt, koennen jedoch unvollstaendig sein und jederzeit ohne vorherige Ankuendigung geaendert werden. Es wird keine ausdrueckliche oder konkludente Gewaehr fuer die Richtigkeit, Vollstaendigkeit oder Aktualitaet der Inhalte uebernommen. Dieses Dokument enthaelt zukunftsgerichtete Aussagen, die auf aktuellen Annahmen und Erwartungen beruhen und mit erheblichen Risiken und Unsicherheiten verbunden sind. Die tatsaechlichen Ergebnisse koennen wesentlich von den dargestellten abweichen. Eine Investitionsentscheidung sollte ausschliesslich auf Grundlage weitergehender, rechtlich verbindlicher Unterlagen sowie unter Hinzuziehung eigener rechtlicher, steuerlicher und finanzieller Beratung getroffen werden. Soweit gesetzlich zulaessig, wird jede Haftung des Team Breakpilot sowie seiner Mitglieder fuer etwaige Schaeden, die direkt oder indirekt aus der Nutzung dieses Dokuments entstehen, ausgeschlossen. Dieses Dokument ist vertraulich und ausschliesslich fuer den vorgesehenen Empfaenger bestimmt. Eine Weitergabe, Vervielfaeltigung oder Veroeffentlichung ist ohne vorherige schriftliche Zustimmung nicht gestattet.'
? 'Dieses Dokument dient ausschließlich Informationszwecken und stellt weder ein Angebot zum Verkauf noch eine Aufforderung zum Kauf von Anteilen oder Wertpapieren dar. Die enthaltenen Informationen wurden vom Team Breakpilot (Gründerteam, noch keine Gesellschaft gegründet) nach bestem Wissen und Gewissen erstellt, können jedoch unvollständig sein und jederzeit ohne vorherige Ankündigung geändert werden. Es wird keine ausdrückliche oder konkludente Gewähr für die Richtigkeit, Vollständigkeit oder Aktualität der Inhalte übernommen. Dieses Dokument enthält zukunftsgerichtete Aussagen, die auf aktuellen Annahmen und Erwartungen beruhen und mit erheblichen Risiken und Unsicherheiten verbunden sind. Die tatsächlichen Ergebnisse können wesentlich von den dargestellten abweichen. Eine Investitionsentscheidung sollte ausschließlich auf Grundlage weitergehender, rechtlich verbindlicher Unterlagen sowie unter Hinzuziehung eigener rechtlicher, steuerlicher und finanzieller Beratung getroffen werden. Soweit gesetzlich zulässig, wird jede Haftung des Team Breakpilot sowie seiner Mitglieder für etwaige Schäden, die direkt oder indirekt aus der Nutzung dieses Dokuments entstehen, ausgeschlossen. Dieses Dokument ist vertraulich und ausschließlich für den vorgesehenen Empfänger bestimmt. Eine Weitergabe, Vervielfältigung oder Veröffentlichung ist ohne vorherige schriftliche Zustimmung nicht gestattet.'
: 'This document is for informational purposes only and does not constitute an offer to sell or a solicitation to purchase shares or securities. The information contained herein was prepared by Team Breakpilot (founding team, no company incorporated yet) to the best of their knowledge, but may be incomplete and subject to change without prior notice. No express or implied warranty is given for the accuracy, completeness or timeliness of the content. This document contains forward-looking statements based on current assumptions and expectations that involve significant risks and uncertainties. Actual results may differ materially. Any investment decision should be based solely on further legally binding documents and with the advice of independent legal, tax and financial counsel. To the extent permitted by law, all liability of Team Breakpilot and its members for any damages arising directly or indirectly from the use of this document is excluded. This document is confidential and intended solely for the designated recipient. Distribution, reproduction or publication without prior written consent is prohibited.'
}
</p>
+165 -145
View File
@@ -3,7 +3,9 @@
import { useState } from 'react'
import { Language } from '@/lib/types'
import { t } from '@/lib/i18n'
import ProjectionFooter from '../ui/ProjectionFooter'
import { useFinancialModel } from '@/lib/hooks/useFinancialModel'
import { useFpKPIs } from '@/lib/hooks/useFpKPIs'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
import FinancialChart from '../ui/FinancialChart'
@@ -21,11 +23,13 @@ type FinTab = 'overview' | 'guv' | 'cashflow'
interface FinancialsSlideProps {
lang: Language
investorId: string | null
preferredScenarioId?: string | null
isWandeldarlehen?: boolean
}
export default function FinancialsSlide({ lang, investorId }: FinancialsSlideProps) {
export default function FinancialsSlide({ lang, investorId, preferredScenarioId, isWandeldarlehen }: FinancialsSlideProps) {
const i = t(lang)
const fm = useFinancialModel(investorId)
const fm = useFinancialModel(investorId, preferredScenarioId)
const [activeTab, setActiveTab] = useState<FinTab>('overview')
const de = lang === 'de'
@@ -33,6 +37,18 @@ export default function FinancialsSlide({ lang, investorId }: FinancialsSlidePro
const summary = activeResults?.summary
const lastResult = activeResults?.results[activeResults.results.length - 1]
// KPI cards from fp_* tables (source of truth)
const { last: fpLast, kpis: fpKPIs } = useFpKPIs(isWandeldarlehen)
const kpiArr = fpLast?.arr || summary?.final_arr || 0
const kpiCustomers = fpLast?.customers || summary?.final_customers || 0
const kpiEbit = fpKPIs?.y2029?.ebit // First profitable year
const kpiBreakEven = (() => {
for (const y of [2026, 2027, 2028, 2029, 2030]) {
if ((fpKPIs[`y${y}`]?.ebit || 0) > 0) return y
}
return 0
})()
// Build scenario color map
const scenarioColors: Record<string, string> = {}
fm.scenarios.forEach(s => { scenarioColors[s.id] = s.color })
@@ -46,7 +62,7 @@ export default function FinancialsSlide({ lang, investorId }: FinancialsSlidePro
const initialFunding = (fm.activeScenario?.assumptions.find(a => a.key === 'initial_funding')?.value as number) || 200000
const tabs: { id: FinTab; label: string }[] = [
{ id: 'overview', label: de ? 'Uebersicht' : 'Overview' },
{ id: 'overview', label: de ? 'Übersicht' : 'Overview' },
{ id: 'guv', label: de ? 'GuV (Jahres)' : 'P&L (Annual)' },
{ id: 'cashflow', label: de ? 'Cashflow & Finanzbedarf' : 'Cashflow & Funding' },
]
@@ -72,9 +88,9 @@ export default function FinancialsSlide({ lang, investorId }: FinancialsSlidePro
<div className="grid grid-cols-2 md:grid-cols-4 gap-2 mb-3">
<KPICard
label={`ARR 2030`}
value={summary ? Math.round(summary.final_arr / 1_000_000 * 10) / 10 : 0}
suffix=" Mio."
decimals={1}
value={kpiArr >= 1_000_000 ? Math.round(kpiArr / 1_000_000 * 10) / 10 : Math.round(kpiArr / 1000)}
suffix={kpiArr >= 1_000_000 ? ' Mio.' : 'k'}
decimals={kpiArr >= 1_000_000 ? 1 : 0}
trend="up"
color="#6366f1"
delay={0.1}
@@ -82,28 +98,29 @@ export default function FinancialsSlide({ lang, investorId }: FinancialsSlidePro
/>
<KPICard
label={de ? 'Kunden 2030' : 'Customers 2030'}
value={summary?.final_customers || 0}
value={kpiCustomers}
trend="up"
color="#22c55e"
delay={0.15}
/>
<FadeInView delay={0.2}>
<div className="bg-white/[0.06] backdrop-blur-xl border border-white/10 rounded-2xl p-3 text-center">
<p className="text-[10px] uppercase tracking-wider text-white/40 mb-1">Break-Even</p>
<p className="text-2xl font-bold text-white">{kpiBreakEven || '—'}</p>
<span className={`text-xs ${kpiBreakEven && kpiBreakEven <= 2029 ? 'text-emerald-400' : 'text-red-400'}`}>
{kpiBreakEven && kpiBreakEven <= 2029 ? '↑' : kpiBreakEven ? '↓' : ''}
</span>
</div>
</FadeInView>
<KPICard
label="Break-Even"
value={summary?.break_even_month || 0}
suffix={de ? ' Mo.' : ' mo.'}
trend={summary?.break_even_month && summary.break_even_month <= 24 ? 'up' : 'down'}
color="#eab308"
delay={0.2}
subLabel={summary?.break_even_month ? `~${Math.ceil((summary.break_even_month) / 12) + 2025}` : ''}
/>
<KPICard
label="LTV/CAC"
value={summary?.final_ltv_cac || 0}
suffix="x"
label="EBIT 2030"
value={fpLast?.ebit ? Math.round(fpLast.ebit / 1_000_000 * 10) / 10 : 0}
suffix=" Mio."
decimals={1}
trend={(summary?.final_ltv_cac || 0) >= 3 ? 'up' : 'down'}
trend={(fpLast?.ebit || 0) > 0 ? 'up' : 'down'}
color="#a855f7"
delay={0.25}
subLabel="EUR"
/>
</div>
@@ -116,7 +133,7 @@ export default function FinancialsSlide({ lang, investorId }: FinancialsSlidePro
className={`px-3 py-1.5 rounded-lg text-xs transition-all
${activeTab === tab.id
? 'bg-indigo-500/20 text-indigo-300 border border-indigo-500/30'
: 'bg-white/[0.04] text-white/40 border border-transparent hover:text-white/60 hover:bg-white/[0.06]'
: 'bg-white/[0.04] text-white/40 border border-transparent hover:text-white/60 hover:bg-white/[0.06] animate-[pulse_3s_ease-in-out_infinite]'
}`}
>
{tab.label}
@@ -124,73 +141,98 @@ export default function FinancialsSlide({ lang, investorId }: FinancialsSlidePro
))}
</div>
{/* Main content: 3-column layout */}
<div className="grid md:grid-cols-12 gap-3">
{/* Left: Charts (8 columns) */}
<div className="md:col-span-8 space-y-3">
{/* Main content: full width */}
<div className="space-y-3">
{/* TAB: Overview — monatlicher Chart + Waterfall + Unit Economics */}
{/* TAB: Overview — annual charts from fp_* */}
{activeTab === 'overview' && (
<>
{/* Revenue vs Costs */}
<FadeInView delay={0.1}>
<div className="bg-white/[0.05] backdrop-blur-xl border border-white/10 rounded-2xl p-3">
<div className="flex items-center justify-between mb-2">
<p className="text-xs text-white/40">
{de ? 'Umsatz vs. Kosten (60 Monate)' : 'Revenue vs. Costs (60 months)'}
</p>
<div className="flex items-center gap-3 text-[9px]">
<span className="flex items-center gap-1"><span className="w-2 h-0.5 bg-indigo-500 inline-block" /> {de ? 'Umsatz' : 'Revenue'}</span>
<span className="flex items-center gap-1"><span className="w-2 h-0.5 bg-red-400 inline-block" style={{ borderBottom: '1px dashed' }} /> {de ? 'Kosten' : 'Costs'}</span>
<span className="flex items-center gap-1"><span className="w-2 h-0.5 bg-emerald-500 inline-block" /> {de ? 'Kunden' : 'Customers'}</span>
<div className="bg-white/[0.05] backdrop-blur-xl border border-white/10 rounded-2xl p-4">
<p className="text-xs text-white/40 mb-3">{de ? 'Umsatz vs. Kosten (pro Jahr) · Quelle: Finanzplan' : 'Revenue vs. Costs (per year) · Source: Financial Plan'}</p>
<div className="grid grid-cols-5 gap-3 items-end h-40">
{[2026,2027,2028,2029,2030].map((y, idx) => {
const rev = fpKPIs[`y${y}`]?.revenue || 0
const costs = rev - (fpKPIs[`y${y}`]?.ebit || 0)
const cust = fpKPIs[`y${y}`]?.customers || 0
const maxVal = Math.max(...[2026,2027,2028,2029,2030].map(yr => Math.max(fpKPIs[`y${yr}`]?.revenue || 0, (fpKPIs[`y${yr}`]?.revenue || 0) - (fpKPIs[`y${yr}`]?.ebit || 0))), 1)
return (
<div key={idx} className="flex flex-col items-center gap-1">
<div className="flex items-end gap-1 w-full justify-center" style={{ height: '110px' }}>
<div className="w-7 bg-indigo-500/60 rounded-t" style={{ height: `${(rev / maxVal) * 100}px` }}>
<div className="text-[10px] text-indigo-300 text-center -mt-4 whitespace-nowrap font-semibold">{rev >= 1000000 ? `${(rev/1000000).toFixed(1)}M` : `${Math.round(rev/1000)}k`}</div>
</div>
<div className="w-7 bg-red-500/40 rounded-t" style={{ height: `${(costs / maxVal) * 100}px` }}>
<div className="text-[10px] text-red-300 text-center -mt-4 whitespace-nowrap font-semibold">{costs >= 1000000 ? `${(costs/1000000).toFixed(1)}M` : `${Math.round(costs/1000)}k`}</div>
</div>
</div>
<FinancialChart
activeResults={activeResults}
compareResults={compareResults}
compareMode={fm.compareMode}
scenarioColors={scenarioColors}
lang={lang}
/>
<span className="text-xs text-white/50 font-medium">{y}</span>
<span className="text-[9px] text-emerald-400/60">{cust} {de ? 'Kd.' : 'cust.'}</span>
</div>
)
})}
</div>
<div className="flex justify-center gap-6 mt-2 text-[10px]">
<span className="flex items-center gap-1"><span className="w-3 h-2 bg-indigo-500/60 rounded inline-block" /> {de ? 'Umsatz' : 'Revenue'}</span>
<span className="flex items-center gap-1"><span className="w-3 h-2 bg-red-500/40 rounded inline-block" /> {de ? 'Kosten' : 'Costs'}</span>
</div>
</div>
</FadeInView>
<div className="grid md:grid-cols-2 gap-3">
{/* EBIT + Liquidität */}
<FadeInView delay={0.2}>
<div className="bg-white/[0.05] backdrop-blur-xl border border-white/10 rounded-2xl p-3">
<p className="text-xs text-white/40 mb-2">
{de ? 'Cash-Flow (Quartal)' : 'Cash Flow (Quarterly)'}
</p>
{activeResults && <WaterfallChart results={activeResults.results} lang={lang} />}
<div className="bg-white/[0.05] backdrop-blur-xl border border-white/10 rounded-2xl p-4">
<p className="text-xs text-white/40 mb-3">EBIT & {de ? 'Liquidität' : 'Cash'}</p>
<div className="grid grid-cols-5 gap-1 items-end h-28">
{[2026,2027,2028,2029,2030].map((y, idx) => {
const ebit = fpKPIs[`y${y}`]?.ebit || 0
const liq = fpKPIs[`y${y}`]?.liquiditaet || 0
const maxAbs = Math.max(...[2026,2027,2028,2029,2030].map(yr => Math.max(Math.abs(fpKPIs[`y${yr}`]?.ebit || 0), Math.abs(fpKPIs[`y${yr}`]?.liquiditaet || 0))), 1)
return (
<div key={idx} className="flex flex-col items-center gap-0.5">
<div className="flex items-end gap-0.5 justify-center" style={{ height: '80px' }}>
<div className={`w-5 ${ebit >= 0 ? 'bg-emerald-500/60 rounded-t' : 'bg-red-500/60 rounded-b'}`} style={{ height: `${Math.max(Math.abs(ebit)/maxAbs * 70, 2)}px` }} />
<div className={`w-5 ${liq >= 0 ? 'bg-cyan-500/60 rounded-t' : 'bg-red-500/40 rounded-b'}`} style={{ height: `${Math.max(Math.abs(liq)/maxAbs * 70, 2)}px` }} />
</div>
<span className="text-[9px] text-white/40">{y}</span>
</div>
)
})}
</div>
<div className="flex justify-center gap-4 mt-2 text-[9px]">
<span className="flex items-center gap-1"><span className="w-2 h-2 bg-emerald-500/60 rounded inline-block" /> EBIT</span>
<span className="flex items-center gap-1"><span className="w-2 h-2 bg-cyan-500/60 rounded inline-block" /> {de ? 'Liquidität' : 'Cash'}</span>
</div>
</div>
</FadeInView>
{/* Key Metrics */}
<FadeInView delay={0.25}>
<div className="space-y-3">
<div className="bg-white/[0.05] backdrop-blur-xl border border-white/10 rounded-2xl p-3 flex justify-center">
<RunwayGauge
months={lastResult?.runway_months || 0}
size={120}
label={de ? 'Runway (Monate)' : 'Runway (months)'}
/>
<div className="bg-white/[0.05] backdrop-blur-xl border border-white/10 rounded-2xl p-4">
<p className="text-xs text-white/40 mb-3">Unit Economics (2030)</p>
<div className="grid grid-cols-2 gap-3">
{[
{ label: 'ACV', value: fpLast?.arpu ? `${fpLast.arpu.toLocaleString('de-DE')} EUR` : '—', color: 'text-indigo-300' },
{ label: 'Gross Margin', value: fpLast?.grossMargin ? `${fpLast.grossMargin}%` : '—', color: 'text-emerald-300' },
{ label: 'NRR', value: fpLast?.nrr ? `${fpLast.nrr}%` : '—', color: 'text-purple-300' },
{ label: 'EBIT Margin', value: fpLast?.ebitMargin ? `${fpLast.ebitMargin}%` : '—', color: 'text-amber-300' },
].map((m, idx) => (
<div key={idx} className="text-center bg-white/[0.03] rounded-lg p-2">
<p className="text-[10px] text-white/40 uppercase tracking-wider">{m.label}</p>
<p className={`text-lg font-bold ${m.color}`}>{m.value}</p>
</div>
))}
</div>
{lastResult && (
<UnitEconomicsCards
cac={lastResult.cac_eur}
ltv={lastResult.ltv_eur}
ltvCacRatio={lastResult.ltv_cac_ratio}
grossMargin={lastResult.gross_margin_pct}
churnRate={fm.activeScenario?.assumptions.find(a => a.key === 'churn_rate_monthly')?.value as number || 3}
lang={lang}
/>
)}
</div>
</FadeInView>
</div>
</>
)}
{/* TAB: GuV — Annual P&L Table */}
{activeTab === 'guv' && activeResults && (
{/* TAB: GuV — from fp_guv */}
{activeTab === 'guv' && (
<FadeInView delay={0.1}>
<div className="bg-white/[0.05] backdrop-blur-xl border border-white/10 rounded-2xl p-4">
<div className="flex items-center justify-between mb-3">
@@ -198,101 +240,79 @@ export default function FinancialsSlide({ lang, investorId }: FinancialsSlidePro
{de ? 'Gewinn- und Verlustrechnung (5 Jahre)' : 'Profit & Loss Statement (5 Years)'}
</p>
<p className="text-[9px] text-white/20">
{de ? 'Alle Werte in EUR' : 'All values in EUR'}
{de ? 'Alle Werte in EUR · Quelle: Finanzplan' : 'All values in EUR · Source: Financial Plan'}
</p>
</div>
<AnnualPLTable results={activeResults.results} lang={lang} />
<table className="w-full text-xs">
<thead>
<tr className="border-b border-white/10">
<th className="text-left py-2 text-white/40 font-medium"></th>
{[2026,2027,2028,2029,2030].map(y => <th key={y} className="text-right py-2 px-2 text-white/40 font-medium">{y}</th>)}
</tr>
</thead>
<tbody>
{[
{ label: de ? 'Umsatzerlöse' : 'Revenue', key: 'revenue', bold: true },
{ label: de ? 'Personalkosten' : 'Personnel', key: 'personal', bold: false },
{ label: 'EBIT', key: 'ebit', bold: true },
{ label: de ? 'Steuern' : 'Taxes', key: 'steuern', bold: false },
{ label: de ? 'Jahresüberschuss' : 'Net Income', key: 'netIncome', bold: true },
].map((row, idx) => (
<tr key={idx} className={`border-b border-white/[0.03] ${row.bold ? 'bg-white/[0.02]' : ''}`}>
<td className={`py-1.5 ${row.bold ? 'font-bold text-white/70' : 'text-white/50'}`}>{row.label}</td>
{[2026,2027,2028,2029,2030].map(y => {
const v = fpKPIs[`y${y}`]?.[row.key as keyof typeof fpKPIs['y2026']] || 0
const num = typeof v === 'number' ? v : 0
return (
<td key={y} className={`text-right py-1.5 px-2 font-mono ${num < 0 ? 'text-red-400' : row.bold ? 'text-white/70 font-bold' : 'text-white/50'}`}>
{num === 0 ? '—' : (num >= 1000000 || num <= -1000000) ? `${(num/1000000).toFixed(1)}M` : `${Math.round(num/1000)}k`}
</td>
)
})}
</tr>
))}
</tbody>
</table>
</div>
</FadeInView>
)}
{/* TAB: Cashflow & Finanzbedarf */}
{activeTab === 'cashflow' && activeResults && (
{/* TAB: Cashflow — from fp_* */}
{activeTab === 'cashflow' && (
<FadeInView delay={0.1}>
<div className="bg-white/[0.05] backdrop-blur-xl border border-white/10 rounded-2xl p-4">
<p className="text-xs text-white/40 mb-3">
{de ? 'Jaehrlicher Cashflow & Finanzbedarf' : 'Annual Cash Flow & Funding Requirements'}
{de ? 'Liquidität & Cashflow (5 Jahre) · Quelle: Finanzplan' : 'Liquidity & Cash Flow (5 Years) · Source: Financial Plan'}
</p>
<AnnualCashflowChart
results={activeResults.results}
initialFunding={initialFunding}
lang={lang}
/>
<div className="grid grid-cols-5 gap-2 items-end h-48">
{[2026,2027,2028,2029,2030].map((y, idx) => {
const liq = fpKPIs[`y${y}`]?.liquiditaet || 0
const ebit = fpKPIs[`y${y}`]?.ebit || 0
const maxAbs = Math.max(...[2026,2027,2028,2029,2030].map(yr => Math.abs(fpKPIs[`y${yr}`]?.liquiditaet || 0)), 1)
const h = Math.abs(liq) / maxAbs * 140
return (
<div key={idx} className="flex flex-col items-center">
<div className="text-[8px] text-white/40 mb-1">{liq >= 1000000 ? `${(liq/1000000).toFixed(1)}M` : liq <= -1000000 ? `${(liq/1000000).toFixed(1)}M` : `${Math.round(liq/1000)}k`}</div>
<div className="w-12 flex flex-col justify-end" style={{ height: '150px' }}>
<div className={`${liq >= 0 ? 'bg-emerald-500/60 rounded-t' : 'bg-red-500/60 rounded-b'} w-full`} style={{ height: `${Math.max(h, 4)}px` }} />
</div>
</FadeInView>
)}
</div>
{/* Right: Controls (4 columns) */}
<div className="md:col-span-4 space-y-3">
{/* Scenario Switcher */}
<FadeInView delay={0.15}>
<div className="bg-white/[0.05] backdrop-blur-xl border border-white/10 rounded-2xl p-3">
<ScenarioSwitcher
scenarios={fm.scenarios}
activeId={fm.activeScenarioId}
compareMode={fm.compareMode}
onSelect={(id) => {
fm.setActiveScenarioId(id)
}}
onToggleCompare={() => {
if (!fm.compareMode) {
fm.computeAll()
}
fm.setCompareMode(!fm.compareMode)
}}
lang={lang}
/>
</div>
</FadeInView>
{/* Assumption Sliders */}
<FadeInView delay={0.2}>
<div className="bg-white/[0.05] backdrop-blur-xl border border-white/10 rounded-2xl p-3">
<p className="text-[10px] text-white/40 uppercase tracking-wider mb-2">
{i.financials.adjustAssumptions}
</p>
{fm.activeScenario && (
<FinancialSliders
assumptions={fm.activeScenario.assumptions}
onAssumptionChange={(key, value) => {
if (fm.activeScenarioId) {
fm.updateAssumption(fm.activeScenarioId, key, value)
}
}}
lang={lang}
/>
)}
{fm.computing && (
<div className="flex items-center gap-2 mt-2 text-[10px] text-indigo-400">
<div className="w-3 h-3 border border-indigo-400 border-t-transparent rounded-full animate-spin" />
{de ? 'Berechne...' : 'Computing...'}
</div>
)}
{/* Snapshot status + reset */}
{investorId && (
<div className="flex items-center justify-between mt-2 pt-2 border-t border-white/5">
<span className="text-[9px] text-white/30">
{fm.snapshotStatus === 'saving' && (de ? 'Speichere...' : 'Saving...')}
{fm.snapshotStatus === 'saved' && (de ? 'Ihre Aenderungen gespeichert' : 'Your changes saved')}
{fm.snapshotStatus === 'restored' && (de ? 'Ihre Werte geladen' : 'Your values restored')}
{fm.snapshotStatus === 'default' && (de ? 'Standardwerte' : 'Defaults')}
<span className="text-[10px] text-white/40 mt-1">{y}</span>
<span className={`text-[8px] mt-0.5 ${ebit >= 0 ? 'text-emerald-400/60' : 'text-red-400/60'}`}>
EBIT: {ebit >= 1000000 ? `${(ebit/1000000).toFixed(1)}M` : `${Math.round(ebit/1000)}k`}
</span>
{fm.snapshotStatus !== 'default' && (
<button
onClick={() => fm.activeScenarioId && fm.resetToDefaults(fm.activeScenarioId)}
className="text-[9px] text-white/40 hover:text-white/70 transition-colors"
>
{de ? 'Zuruecksetzen' : 'Reset to defaults'}
</button>
)}
</div>
)}
)
})}
</div>
<div className="flex justify-center gap-6 mt-3 text-[10px]">
<span className="flex items-center gap-1"><span className="w-3 h-2 bg-emerald-500/60 rounded inline-block" /> {de ? 'Liquidität (positiv)' : 'Cash (positive)'}</span>
<span className="flex items-center gap-1"><span className="w-3 h-2 bg-red-500/60 rounded inline-block" /> {de ? 'Liquidität (negativ)' : 'Cash (negative)'}</span>
</div>
</div>
</FadeInView>
)}
</div>
</div>
<ProjectionFooter lang={lang} />
</div>
)
}
File diff suppressed because it is too large Load Diff
+11 -57
View File
@@ -9,58 +9,31 @@ import { Target, Users, Handshake, Megaphone, Building2, GraduationCap } from 'l
interface GTMSlideProps {
lang: Language
isWandeldarlehen?: boolean
}
export default function GTMSlide({ lang }: GTMSlideProps) {
export default function GTMSlide({ lang, isWandeldarlehen }: GTMSlideProps) {
const i = t(lang)
const de = lang === 'de'
const phases = [
{
phase: de ? 'Phase 1: Pilot (2026)' : 'Phase 1: Pilot (2026)',
color: 'border-indigo-500/30 bg-indigo-500/5',
textColor: 'text-indigo-400',
items: [
de ? 'Direktvertrieb an 5-20 KMU in DACH' : 'Direct sales to 5-20 SMEs in DACH',
de ? 'Fokus: Gesundheitswesen, Finanzdienstleister, Rechtsanwaelte' : 'Focus: Healthcare, Financial Services, Law Firms',
de ? 'Persoenliches Onboarding, White-Glove-Service' : 'Personal onboarding, white-glove service',
de ? 'Case Studies und Referenzkunden aufbauen' : 'Build case studies and reference customers',
],
},
{
phase: de ? 'Phase 2: Skalierung (2027)' : 'Phase 2: Scale (2027)',
color: 'border-purple-500/30 bg-purple-500/5',
textColor: 'text-purple-400',
items: [
de ? 'Channel-Partnerschaften mit IT-Systemhaeusern' : 'Channel partnerships with IT system integrators',
de ? 'IHK- und Handwerkskammer-Kooperationen' : 'Chamber of Commerce & Industry partnerships',
de ? 'Content Marketing: Compliance-Webinare, Whitepaper' : 'Content marketing: Compliance webinars, whitepapers',
de ? 'Zielkunden: 50-200 in regulierten Branchen' : 'Target: 50-200 customers in regulated industries',
],
},
{
phase: de ? 'Phase 3: Expansion (2028+)' : 'Phase 3: Expansion (2028+)',
color: 'border-emerald-500/30 bg-emerald-500/5',
textColor: 'text-emerald-400',
items: [
de ? 'Cloud-Tier fuer groessere Unternehmen (50-500 MA)' : 'Cloud tier for larger companies (50-500 employees)',
de ? 'EU-Expansion: Oesterreich, Schweiz, Benelux, Nordics' : 'EU expansion: Austria, Switzerland, Benelux, Nordics',
de ? 'OEM/Whitelabel fuer Steuerberater und Wirtschaftspruefer' : 'OEM/whitelabel for tax advisors and auditors',
de ? 'Self-Service-Onboarding und PLG-Motion' : 'Self-service onboarding and PLG motion',
],
},
const channelsLean = [
{ icon: Target, label: de ? 'Gründer-Vertrieb' : 'Founder Sales', pct: '60%', desc: de ? 'Persönliches Netzwerk + Beratung' : 'Personal network + consulting' },
{ icon: Megaphone, label: de ? 'Content & SEO' : 'Content & SEO', pct: '25%', desc: de ? 'Webinare, Whitepaper, Fachbeiträge' : 'Webinars, whitepapers, articles' },
{ icon: Users, label: de ? 'Empfehlungen' : 'Referrals', pct: '15%', desc: de ? 'Zufriedene Pilotkunden' : 'Satisfied pilot customers' },
]
const channels = [
const channels1M = [
{ icon: Target, label: de ? 'Direktvertrieb' : 'Direct Sales', pct: '40%', desc: de ? 'Outbound + Inbound, 2 AEs ab 2027' : 'Outbound + Inbound, 2 AEs from 2027' },
{ icon: Handshake, label: de ? 'Channel-Partner' : 'Channel Partners', pct: '30%', desc: de ? 'IT-Haendler, Systemhaeuser, MSPs' : 'IT resellers, system integrators, MSPs' },
{ icon: Handshake, label: de ? 'Channel-Partner' : 'Channel Partners', pct: '30%', desc: de ? 'IT-Händler, Systemhäuser, MSPs' : 'IT resellers, system integrators, MSPs' },
{ icon: Megaphone, label: de ? 'Content & Events' : 'Content & Events', pct: '20%', desc: de ? 'Webinare, Messen (it-sa), SEO' : 'Webinars, trade shows (it-sa), SEO' },
{ icon: Users, label: de ? 'Empfehlungen' : 'Referrals', pct: '10%', desc: de ? 'Bestandskunden-Empfehlungsprogramm' : 'Customer referral program' },
]
const channels = isWandeldarlehen ? channelsLean : channels1M
const idealCustomer = [
{ icon: Building2, label: de ? '10-250 Mitarbeiter' : '10-250 Employees' },
{ icon: GraduationCap, label: de ? 'Regulierte Branche (Gesundheit, Finanzen, Energie, KRITIS)' : 'Regulated Industry (Healthcare, Finance, Energy, Critical Infrastructure)' },
{ icon: GraduationCap, label: de ? 'Produzierende Industrie (Maschinenbau, Automotive, Elektro, Chemie)' : 'Manufacturing Industry (Machinery, Automotive, Electrical, Chemicals)' },
{ icon: Target, label: de ? 'Kein interner Compliance-Officer oder DSB' : 'No Internal Compliance Officer or DPO' },
]
@@ -96,25 +69,6 @@ export default function GTMSlide({ lang }: GTMSlideProps) {
</GlassCard>
</FadeInView>
{/* Phases */}
<div className="grid md:grid-cols-3 gap-4 mb-6">
{phases.map((phase, idx) => (
<FadeInView key={idx} delay={0.2 + idx * 0.1}>
<div className={`border rounded-xl p-4 h-full ${phase.color}`}>
<p className={`text-sm font-bold ${phase.textColor} mb-3`}>{phase.phase}</p>
<ul className="space-y-2">
{phase.items.map((item, iidx) => (
<li key={iidx} className="flex items-start gap-2 text-xs text-white/60">
<span className={`w-1 h-1 rounded-full mt-1.5 ${phase.textColor} bg-current`} />
{item}
</li>
))}
</ul>
</div>
</FadeInView>
))}
</div>
{/* Channel Mix */}
<FadeInView delay={0.5}>
<GlassCard hover={false} className="p-4">
+23 -3
View File
@@ -40,14 +40,18 @@ export default function GlossarySlide({ lang }: GlossarySlideProps) {
],
},
{
title: de ? 'EU-Regulierungen' : 'EU Regulations',
title: de ? 'EU-Regulierungen & Gesetze' : 'EU Regulations & Laws',
color: 'text-cyan-400',
terms: [
{ abbr: 'AI Act', full: de ? 'KI-Verordnung (EU) 2024/1689' : 'AI Regulation (EU) 2024/1689', desc: de ? 'Weltweit erste KI-Regulierung, Risikoklassen für KI-Systeme' : 'World\'s first AI regulation, risk classes for AI systems' },
{ abbr: 'CRA', full: 'Cyber Resilience Act', desc: de ? 'Cybersicherheit für Produkte mit digitalen Elementen, SBOM-Pflicht' : 'Cybersecurity for products with digital elements, SBOM mandatory' },
{ abbr: 'NIS2', full: 'Network and Information Security Directive 2', desc: de ? 'Cybersicherheits-Richtlinie, 30.000+ Unternehmen in DE betroffen' : 'Cybersecurity directive, 30,000+ companies in DE affected' },
{ abbr: 'MVO', full: de ? 'Maschinenverordnung (EU) 2023/1230' : 'Machinery Regulation (EU) 2023/1230', desc: de ? 'CE-Kennzeichnung inkl. Cybersicherheit ab Jan 2027' : 'CE marking incl. cybersecurity from Jan 2027' },
{ abbr: 'FISA 702', full: 'Foreign Intelligence Surveillance Act, Section 702', desc: de ? 'US-Überwachungsgesetz — erlaubt Zugriff auf Daten von Nicht-US-Personen' : 'US surveillance law — allows access to data of non-US persons' },
{ abbr: 'Cloud Act', full: 'Clarifying Lawful Overseas Use of Data Act', desc: de ? 'US-Gesetz — extraterritorialer Datenzugriff auf US-Anbieter' : 'US law — extraterritorial data access to US providers' },
{ abbr: 'BDSG', full: de ? 'Bundesdatenschutzgesetz' : 'Federal Data Protection Act', desc: de ? 'Deutsche Ergänzung zur DSGVO' : 'German supplement to GDPR' },
{ abbr: 'TISAX', full: 'Trusted Information Security Assessment Exchange', desc: de ? 'Informationssicherheits-Standard der Automobilindustrie' : 'Information security standard for automotive industry' },
{ abbr: 'BSI', full: de ? 'Bundesamt für Sicherheit in der Informationstechnik' : 'Federal Office for Information Security', desc: de ? 'Deutsche Cyber-Sicherheitsbehörde' : 'German cybersecurity authority' },
],
},
{
@@ -64,6 +68,22 @@ export default function GlossarySlide({ lang }: GlossarySlideProps) {
{ abbr: 'ROI', full: 'Return on Investment', desc: de ? 'Rendite auf die Investition' : 'Return on investment' },
],
},
{
title: de ? 'Technologie & Plattform' : 'Technology & Platform',
color: 'text-amber-400',
terms: [
{ abbr: 'RAG', full: 'Retrieval Augmented Generation', desc: de ? 'KI-Methode: Wissenssuche + Textgenerierung kombiniert' : 'AI method: knowledge retrieval + text generation combined' },
{ abbr: 'LLM', full: 'Large Language Model', desc: de ? 'Großes Sprachmodell (z.B. GPT, Claude, Qwen)' : 'Large language model (e.g. GPT, Claude, Qwen)' },
{ abbr: 'UCCA', full: 'Use-Case Compliance Assessment', desc: de ? 'Automatische Bewertung von KI-Anwendungsfällen nach AI Act' : 'Automatic assessment of AI use cases per AI Act' },
{ abbr: 'FRIA', full: 'Fundamental Rights Impact Assessment', desc: de ? 'Grundrechte-Folgenabschätzung nach Art. 27 AI Act' : 'Fundamental rights impact assessment per Art. 27 AI Act' },
{ abbr: 'SDK', full: 'Software Development Kit', desc: de ? 'Entwicklungspaket zur Integration in Kundensysteme' : 'Development kit for integration into customer systems' },
{ abbr: 'OWASP', full: 'Open Web Application Security Project', desc: de ? 'Open-Source-Sicherheitsstandards für Webanwendungen' : 'Open-source security standards for web applications' },
{ abbr: 'NIST', full: 'National Institute of Standards and Technology', desc: de ? 'US-Behörde für Technologiestandards (auch international anerkannt)' : 'US standards body (internationally recognized)' },
{ abbr: 'ENISA', full: 'European Union Agency for Cybersecurity', desc: de ? 'EU-Agentur für Cybersicherheit' : 'EU Agency for Cybersecurity' },
{ abbr: 'CE', full: de ? 'Conformité Européenne' : 'Conformité Européenne', desc: de ? 'EU-Konformitätskennzeichnung für Produkte' : 'EU conformity marking for products' },
{ abbr: 'RFQ', full: 'Request for Quotation', desc: de ? 'Kundenanfrage / Angebotsanforderung' : 'Customer request for quotation' },
],
},
]
return (
@@ -81,11 +101,11 @@ export default function GlossarySlide({ lang }: GlossarySlideProps) {
<h3 className={`text-xs font-bold ${cat.color} uppercase tracking-wider mb-3`}>{cat.title}</h3>
<div className="space-y-2">
{cat.terms.map((term, i) => (
<div key={i} className="flex gap-2">
<div key={i} className="flex gap-2 items-baseline">
<span className={`text-xs font-bold ${cat.color} min-w-[65px] shrink-0`}>{term.abbr}</span>
<div>
<span className="text-xs text-white/70">{term.full}</span>
<span className="text-[10px] text-white/40 ml-1"> {term.desc}</span>
<span className="text-xs text-white/40 ml-1"> {term.desc}</span>
</div>
</div>
))}
@@ -27,8 +27,8 @@ export default function HowItWorksSlide({ lang }: HowItWorksSlideProps) {
</FadeInView>
<div className="relative max-w-4xl mx-auto">
{/* Connection Line */}
<div className="absolute left-8 top-12 bottom-12 w-px bg-gradient-to-b from-blue-500 via-purple-500 to-green-500 hidden md:block" />
{/* Connection Line — behind icons (z-0), icons have z-10 with opaque bg */}
<div className="absolute left-8 top-20 bottom-20 w-px bg-gradient-to-b from-blue-500/40 via-purple-500/40 to-green-500/40 hidden md:block z-0" />
<div className="space-y-8">
{i.howItWorks.steps.map((step, idx) => {
@@ -42,7 +42,7 @@ export default function HowItWorksSlide({ lang }: HowItWorksSlideProps) {
className="flex items-start gap-6 relative"
>
<div className={`
w-16 h-16 rounded-2xl bg-white/[0.06] border border-white/10
w-16 h-16 rounded-2xl bg-[#0c0c1d] border border-white/10
flex items-center justify-center shrink-0 relative z-10
${stepColors[idx]}
`}>
@@ -57,8 +57,8 @@ export default function IntroPresenterSlide({ lang, onStartPresenter, isPresenti
</h1>
<p className="text-lg text-white/60 max-w-lg mx-auto mb-8">
{isDE
? 'Ihr persönlicher KI-Guide durch das BreakPilot ComplAI Pitch Deck. 15 Minuten, alle Fakten, jederzeit unterbrechbar.'
: 'Your personal AI guide through the BreakPilot ComplAI pitch deck. 15 minutes, all facts, interruptible at any time.'}
? 'Ihr persönlicher KI-Guide durch das BreakPilot COMPLAI Pitch Deck. 15 Minuten, alle Fakten, jederzeit unterbrechbar.'
: 'Your personal AI guide through the BreakPilot COMPLAI pitch deck. 15 minutes, all facts, interruptible at any time.'}
</p>
</motion.div>
+12 -12
View File
@@ -22,21 +22,21 @@ interface MarketSourceInfo {
excerpt_en: string
}
// Quellenangaben fuer die Marktzahlen
// Quellenangaben für die Marktzahlen
const marketSources: Record<string, MarketSourceInfo[]> = {
TAM: [
{
name: 'Bottom-Up-Validierung: Echte Umsatzdaten der Top-10 Compliance-Anbieter',
url: 'https://sacra.com/c/vanta/',
date: '2025-2026',
excerpt_de: 'Die Top-10 Compliance-Automation-Anbieter erzielen zusammen ~$1,13 Mrd. Umsatz (Vanta $220M, OneTrust $500M, Drata $100M, Usercentrics $117M, Securiti $76M, DataGuard €52M, Sprinto $38M, heyData €15M, Caralegal €5.8M, Proliance €3.9M). Mit 50+ weiteren Anbietern liegt der Gesamtmarkt bei ~$1,6-2 Mrd. — aktuell nur ~20% des adressierbaren Volumens (Gartner: 80% der Unternehmen managen Compliance noch manuell). Inkl. DevSecOps fuer Manufacturing (~$3,5 Mrd.) ergibt sich ein TAM von $8-12 Mrd.',
excerpt_de: 'Die Top-10 Compliance-Automation-Anbieter erzielen zusammen ~$1,13 Mrd. Umsatz (Vanta $220M, OneTrust $500M, Drata $100M, Usercentrics $117M, Securiti $76M, DataGuard €52M, Sprinto $38M, heyData €15M, Caralegal €5.8M, Proliance €3.9M). Mit 50+ weiteren Anbietern liegt der Gesamtmarkt bei ~$1,6-2 Mrd. — aktuell nur ~20% des adressierbaren Volumens (Gartner: 80% der Unternehmen managen Compliance noch manuell). Inkl. DevSecOps für Manufacturing (~$3,5 Mrd.) ergibt sich ein TAM von $8-12 Mrd.',
excerpt_en: 'The top 10 compliance automation providers generate ~$1.13B combined revenue (Vanta $220M, OneTrust $500M, Drata $100M, Usercentrics $117M, Securiti $76M, DataGuard €52M, Sprinto $38M, heyData €15M, Caralegal €5.8M, Proliance €3.9M). With 50+ additional vendors, the total market is ~$1.6-2B — currently only ~20% of addressable volume (Gartner: 80% manage compliance manually). Incl. DevSecOps for manufacturing (~$3.5B), the TAM is $8-12B.',
},
{
name: 'Grand View Research — GRC Market Report 2024',
url: 'https://www.grandviewresearch.com/industry-analysis/governance-risk-management-compliance-market',
date: '2024',
excerpt_de: 'Der globale GRC-Software-Markt wurde 2023 auf 11,8 Mrd. USD bewertet, CAGR 13,8%. Die Compliance-Automation-Welle (Vanta, Drata) zeigt 30-45% Wachstum p.a. — deutlich ueber dem Branchendurchschnitt.',
excerpt_de: 'Der globale GRC-Software-Markt wurde 2023 auf 11,8 Mrd. USD bewertet, CAGR 13,8%. Die Compliance-Automation-Welle (Vanta, Drata) zeigt 30-45% Wachstum p.a. — deutlich über dem Branchendurchschnitt.',
excerpt_en: 'The global GRC software market was valued at USD 11.8B in 2023, CAGR 13.8%. The compliance automation wave (Vanta, Drata) shows 30-45% p.a. growth — well above industry average.',
},
],
@@ -45,7 +45,7 @@ const marketSources: Record<string, MarketSourceInfo[]> = {
name: 'Bottom-Up: DACH Compliance-Anbieter + NIS2/CRA/AI-Act Expansion',
url: 'https://www.vdma.org/statistics',
date: '2025-2026',
excerpt_de: 'DACH-Compliance-Umsaetze heute: DataGuard €52M + heyData €15M + Proliance €3.9M + Caralegal €5.8M + OneTrust DACH ~€30M + Secjur/andere ~€10M = ~€120M (nur DSGVO-Compliance). NIS2 erweitert die Regulierung auf 30.000+ Unternehmen (bisher 4.500). CRA und AI Act schaffen voellig neue Pflichten fuer Maschinenbauer. DACH-DevSecOps-Markt: +€300-400M. Gesamtes SAM fuer Compliance + Code-Security in DACH Manufacturing: €850M-1,2 Mrd.',
excerpt_de: 'DACH-Compliance-Umsaetze heute: DataGuard €52M + heyData €15M + Proliance €3.9M + Caralegal €5.8M + OneTrust DACH ~€30M + Secjur/andere ~€10M = ~€120M (nur DSGVO-Compliance). NIS2 erweitert die Regulierung auf 30.000+ Unternehmen (bisher 4.500). CRA und AI Act schaffen voellig neue Pflichten für Maschinenbauer. DACH-DevSecOps-Markt: +€300-400M. Gesamtes SAM für Compliance + Code-Security in DACH Manufacturing: €850M-1,2 Mrd.',
excerpt_en: 'DACH compliance revenues today: DataGuard €52M + heyData €15M + Proliance €3.9M + Caralegal €5.8M + OneTrust DACH ~€30M + Secjur/others ~€10M = ~€120M (GDPR compliance only). NIS2 expands regulation to 30,000+ companies (from 4,500). CRA and AI Act create entirely new obligations for manufacturers. DACH DevSecOps market: +€300-400M. Total SAM for compliance + code security in DACH manufacturing: €850M-1.2B.',
},
],
@@ -54,7 +54,7 @@ const marketSources: Record<string, MarketSourceInfo[]> = {
name: 'VDMA Mitgliederstatistik + Wettbewerbs-Benchmarks',
url: 'https://www.vdma.org/mitglieder',
date: '2025-2026',
excerpt_de: 'DACH-weit ca. 5.000 Maschinenbauer mit Eigenentwicklung (VDMA). Bei 10% Marktdurchdringung (~500 Unternehmen) und €14.400/Jahr ARPU (Blended Avg.) ergibt sich ein SOM von €7,2 Mio. Zum Vergleich: Proliance mit 65 Mitarbeitern erreicht €3,9M, heyData mit 58 MA bereits €15M. Mit KI-Automatisierung ist eine hoehere Durchdringung bei niedrigerer Personalintensitaet moeglich.',
excerpt_de: 'DACH-weit ca. 5.000 Maschinenbauer mit Eigenentwicklung (VDMA). Bei 10% Marktdurchdringung (~500 Unternehmen) und €14.400/Jahr ARPU (Blended Avg.) ergibt sich ein SOM von €7,2 Mio. Zum Vergleich: Proliance mit 65 Mitarbeitern erreicht €3,9M, heyData mit 58 MA bereits €15M. Mit KI-Automatisierung ist eine höhere Durchdringung bei niedrigerer Personalintensität möglich.',
excerpt_en: 'Approx. 5,000 DACH machine manufacturers with in-house dev (VDMA). At 10% penetration (~500 companies) and €14,400/yr ARPU (blended avg.), SOM is €7.2M. For comparison: Proliance with 65 employees achieves €3.9M, heyData with 58 employees already €15M. AI automation enables higher penetration with lower headcount intensity.',
},
],
@@ -84,14 +84,14 @@ const pentestMarketSources: Record<string, MarketSourceInfo[]> = {
name: 'MarketsAndMarkets — Application Security Testing Market 2025',
url: 'https://www.marketsandmarkets.com/Market-Reports/application-security-testing-market-150735030.html',
date: '2025',
excerpt_de: 'Der globale AST-Markt (SAST, DAST, IAST, SCA) wird auf $8,5 Mrd. (2025) geschaetzt und soll bis 2030 auf $19,5 Mrd. wachsen (CAGR 18,2%). Hinzu kommt der Pentesting-Markt ($2,7 Mrd.) und der Compliance-Convergence-Anteil ($1,8 Mrd.). Gesamt-TAM fuer integriertes AppSec + Compliance: ~$13 Mrd.',
excerpt_de: 'Der globale AST-Markt (SAST, DAST, IAST, SCA) wird auf $8,5 Mrd. (2025) geschätzt und soll bis 2030 auf $19,5 Mrd. wachsen (CAGR 18,2%). Hinzu kommt der Pentesting-Markt ($2,7 Mrd.) und der Compliance-Convergence-Anteil ($1,8 Mrd.). Gesamt-TAM für integriertes AppSec + Compliance: ~$13 Mrd.',
excerpt_en: 'The global AST market (SAST, DAST, IAST, SCA) is estimated at $8.5B (2025), projected to reach $19.5B by 2030 (CAGR 18.2%). Adding the pentesting market ($2.7B) and compliance convergence share ($1.8B), total TAM for integrated AppSec + compliance: ~$13B.',
},
{
name: 'Gartner — Magic Quadrant for Application Security Testing 2024',
url: 'https://www.gartner.com/reviews/market/application-security-testing',
date: '2024',
excerpt_de: 'Gartner bestaetigt den Trend zur Konvergenz von AppSec und Compliance. Fuehrende Anbieter (Snyk, Veracode, Checkmarx) erreichen zusammen >$850M Umsatz. Der Markt waechst mit 17-20% p.a., getrieben durch regulatorische Anforderungen (CRA, NIS2) und AI-getriebene Entwicklung.',
excerpt_de: 'Gartner bestätigt den Trend zur Konvergenz von AppSec und Compliance. Führende Anbieter (Snyk, Veracode, Checkmarx) erreichen zusammen >$850M Umsatz. Der Markt wächst mit 17-20% p.a., getrieben durch regulatorische Anforderungen (CRA, NIS2) und AI-getriebene Entwicklung.',
excerpt_en: 'Gartner confirms the AppSec-compliance convergence trend. Leading vendors (Snyk, Veracode, Checkmarx) generate >$850M combined revenue. The market grows at 17-20% p.a., driven by regulatory requirements (CRA, NIS2) and AI-driven development.',
},
],
@@ -100,7 +100,7 @@ const pentestMarketSources: Record<string, MarketSourceInfo[]> = {
name: 'Bottom-Up: DACH AppSec + Manufacturing Pentesting',
url: 'https://www.bitkom.org/Marktdaten/ITK-Konjunktur/IT-Markt-Deutschland',
date: '2025-2026',
excerpt_de: 'DACH IT-Security-Markt: €8,2 Mrd. (Bitkom 2025). AppSec-Anteil: ~15% = €1,2 Mrd. Davon Pentesting/DAST/SAST fuer produzierende Industrie: ~€400M. CRA-Pflicht fuer Maschinenbauer erzeugt neue Nachfrage: geschaetzt +€200M bis 2028. SAM fuer integriertes AppSec + Compliance im DACH-Manufacturing: ~€1,6 Mrd.',
excerpt_de: 'DACH IT-Security-Markt: €8,2 Mrd. (Bitkom 2025). AppSec-Anteil: ~15% = €1,2 Mrd. Davon Pentesting/DAST/SAST für produzierende Industrie: ~€400M. CRA-Pflicht für Maschinenbauer erzeugt neue Nachfrage: geschätzt +€200M bis 2028. SAM für integriertes AppSec + Compliance im DACH-Manufacturing: ~€1,6 Mrd.',
excerpt_en: 'DACH IT security market: €8.2B (Bitkom 2025). AppSec share: ~15% = €1.2B. Pentesting/DAST/SAST for manufacturing: ~€400M. CRA obligation for manufacturers creates new demand: est. +€200M by 2028. SAM for integrated AppSec + compliance in DACH manufacturing: ~€1.6B.',
},
],
@@ -225,7 +225,7 @@ export default function MarketSlide({ lang, market }: MarketSlideProps) {
className={`px-4 py-1.5 rounded-full text-xs font-medium transition-all ${
marketView === 'compliance'
? 'bg-indigo-500/20 text-indigo-300 border border-indigo-500/30'
: 'bg-white/[0.04] text-white/40 border border-white/5 hover:bg-white/[0.08]'
: 'bg-white/[0.04] text-white/40 border border-white/5 hover:bg-white/[0.08] animate-[pulse_3s_ease-in-out_infinite]'
}`}
>
{lang === 'de' ? 'Compliance & Code-Security' : 'Compliance & Code Security'}
@@ -235,7 +235,7 @@ export default function MarketSlide({ lang, market }: MarketSlideProps) {
className={`px-4 py-1.5 rounded-full text-xs font-medium transition-all flex items-center gap-1.5 ${
marketView === 'pentesting'
? 'bg-red-500/20 text-red-300 border border-red-500/30'
: 'bg-white/[0.04] text-white/40 border border-white/5 hover:bg-white/[0.08]'
: 'bg-white/[0.04] text-white/40 border border-white/5 hover:bg-white/[0.08] animate-[pulse_3s_ease-in-out_infinite]'
}`}
>
<Shield className="w-3 h-3" />
@@ -304,7 +304,7 @@ export default function MarketSlide({ lang, market }: MarketSlideProps) {
</div>
<p className="text-[10px] text-indigo-400/60 group-hover:text-indigo-400 transition-colors mt-0.5">
{sourceCount} {lang === 'de' ? (sourceCount === 1 ? 'Quelle' : 'Quellen') : (sourceCount === 1 ? 'source' : 'sources')}
{' · '}{lang === 'de' ? 'Klicken fuer Details' : 'Click for details'}
{' · '}{lang === 'de' ? 'Klicken für Details' : 'Click for details'}
</p>
</div>
</div>
@@ -380,7 +380,7 @@ export default function MarketSlide({ lang, market }: MarketSlideProps) {
</div>
<p className="text-[10px] text-red-400/60 group-hover:text-red-400 transition-colors mt-0.5">
{sourceCount} {lang === 'de' ? (sourceCount === 1 ? 'Quelle' : 'Quellen') : (sourceCount === 1 ? 'source' : 'sources')}
{' · '}{lang === 'de' ? 'Klicken fuer Details' : 'Click for details'}
{' · '}{lang === 'de' ? 'Klicken für Details' : 'Click for details'}
</p>
</div>
</div>
@@ -0,0 +1,817 @@
'use client'
import { useState, useEffect, useRef, useMemo, useCallback, Fragment } from 'react'
import { Language } from '@/lib/types'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
interface MilestonesSlideProps { lang: Language }
const MONO: React.CSSProperties = {
fontFamily: '"JetBrains Mono","SF Mono",ui-monospace,monospace',
fontVariantNumeric: 'tabular-nums',
}
const CSS_KF = `
@keyframes msFlow { 0%{stroke-dashoffset:0} 100%{stroke-dashoffset:-18} }
@keyframes msFadeIn { from{opacity:0} to{opacity:1} }
@keyframes msScaleIn { from{opacity:0;transform:scale(.94)} to{opacity:1;transform:scale(1)} }
@keyframes msHeadingDark {
0%,100%{text-shadow:0 0 22px rgba(167,139,250,.3)}
50% {text-shadow:0 0 40px rgba(167,139,250,.6)}
}
@keyframes msHeadingLight {
0%,100%{text-shadow:0 0 22px rgba(124,58,237,.15)}
50% {text-shadow:0 0 36px rgba(124,58,237,.30)}
}
@keyframes msPulse {
0%,100%{r:9;opacity:.4}
50% {r:14;opacity:.05}
}
`
// ── Light mode hook ───────────────────────────────────────────────────────────
function useIsLight() {
const [isLight, setIsLight] = useState(false)
useEffect(() => {
const check = () => setIsLight(document.documentElement.classList.contains('theme-light'))
check()
const obs = new MutationObserver(check)
obs.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] })
return () => obs.disconnect()
}, [])
return isLight
}
// ── Themes ────────────────────────────────────────────────────────────────────
const THEMES = {
dark: {
key: 'dark' as const,
bg: 'radial-gradient(ellipse at 50% 25%, #1a0f34 0%, #0e0720 55%, #050210 100%)',
ambient: 'radial-gradient(ellipse, rgba(167,139,250,.18), transparent 65%)',
stars: true,
fg: '#f7f5fc',
fgSoft: 'rgba(236,233,247,.82)',
fgMid: 'rgba(236,233,247,.72)',
fgMuted: 'rgba(236,233,247,.62)',
fgFaint: 'rgba(236,233,247,.55)',
fgGhost: 'rgba(236,233,247,.45)',
fgWhisper: 'rgba(236,233,247,.4)',
accent: '#a78bfa',
accent80: 'rgba(167,139,250,.8)',
accent70: 'rgba(167,139,250,.7)',
accent50: 'rgba(167,139,250,.5)',
accent40: 'rgba(167,139,250,.4)',
accent20: 'rgba(167,139,250,.2)',
headingGrad: 'linear-gradient(90deg, #e9e2ff, #a78bfa 50%, #e9e2ff)',
headingAnim: 'msHeadingDark 4s ease-in-out infinite',
heuteText: '#e4d4ff',
heutePillBg: 'rgba(14,8,28,.95)',
heuteCore: '#f0e9ff',
done: '#4ade80',
doneBright: '#86efac',
doneDeep: '#166534',
doneSolid: '#22c55e',
cardBase: 'rgba(14,8,28,',
cardBaseA: '.9',
cardBaseAH: '.95',
cardTintTop: '18', cardTintTopH: '2e',
cardTintMid: '08', cardTintMidH: '14',
cardShadowSoft: '0 10px 24px rgba(0,0,0,.45)',
cardShadowLift: (t: string) => `0 20px 44px ${t}33, 0 0 0 1px ${t}66, inset 0 1px 0 ${t}66`,
statTintTop: '18', statTintTopH: '2a',
statTintMid: '06',
statShadowSoft: '0 10px 24px rgba(0,0,0,.45)',
statShadowLift: (t: string) => `0 18px 40px ${t}33, 0 0 0 1px ${t}55, inset 0 1px 0 ${t}55`,
modalScrim: 'rgba(5,2,16,.75)',
modalBgMid: 'rgba(20,10,40,.97)',
modalBgLow: 'rgba(14,8,28,.98)',
modalShadow: (t: string) => `0 30px 80px rgba(0,0,0,.65), 0 0 60px ${t}33, inset 0 1px 0 ${t}55`,
bulletBg: 'rgba(0,0,0,.3)',
progressTrackBg: 'rgba(255,255,255,.08)',
progressTrackBorder: 'rgba(167,139,250,.2)',
dotTodoDeep: '#1a0f34',
dotLitHi: 'rgba(255,255,255,.5)',
dotSoftHi: 'rgba(255,255,255,.3)',
sparkOp: 0.45,
},
light: {
key: 'light' as const,
bg: 'radial-gradient(ellipse at 50% 12%, #ffffff 0%, #f5efff 55%, #ebdfff 100%)',
ambient: 'radial-gradient(ellipse, rgba(124,58,237,.14), transparent 65%)',
stars: false,
fg: '#1a0f34',
fgSoft: 'rgba(26,15,52,.85)',
fgMid: 'rgba(26,15,52,.72)',
fgMuted: 'rgba(26,15,52,.62)',
fgFaint: 'rgba(26,15,52,.50)',
fgGhost: 'rgba(26,15,52,.40)',
fgWhisper: 'rgba(26,15,52,.32)',
accent: '#7c3aed',
accent80: 'rgba(124,58,237,.8)',
accent70: 'rgba(124,58,237,.75)',
accent50: 'rgba(124,58,237,.55)',
accent40: 'rgba(124,58,237,.4)',
accent20: 'rgba(124,58,237,.18)',
headingGrad: 'linear-gradient(90deg, #3b0e7a, #7c3aed 50%, #3b0e7a)',
headingAnim: 'msHeadingLight 4s ease-in-out infinite',
heuteText: '#4c1d95',
heutePillBg: 'rgba(255,255,255,.98)',
heuteCore: '#7c3aed',
done: '#16a34a',
doneBright: '#4ade80',
doneDeep: '#14532d',
doneSolid: '#22c55e',
cardBase: 'rgba(255,255,255,',
cardBaseA: '.92',
cardBaseAH: '.98',
cardTintTop: '22', cardTintTopH: '3a',
cardTintMid: '10', cardTintMidH: '1c',
cardShadowSoft: '0 10px 24px rgba(59,26,122,.10), 0 2px 6px rgba(59,26,122,.06)',
cardShadowLift: (t: string) => `0 20px 44px ${t}38, 0 0 0 1px ${t}77, inset 0 1px 0 rgba(255,255,255,.9)`,
statTintTop: '1e', statTintTopH: '34',
statTintMid: '08',
statShadowSoft: '0 10px 24px rgba(59,26,122,.10), 0 2px 6px rgba(59,26,122,.06)',
statShadowLift: (t: string) => `0 18px 40px ${t}38, 0 0 0 1px ${t}77, inset 0 1px 0 rgba(255,255,255,.9)`,
modalScrim: 'rgba(40,20,80,.28)',
modalBgMid: 'rgba(255,255,255,.98)',
modalBgLow: 'rgba(250,247,255,.98)',
modalShadow: (t: string) => `0 30px 80px rgba(59,26,122,.25), 0 0 60px ${t}33, inset 0 1px 0 rgba(255,255,255,.9)`,
bulletBg: 'rgba(124,58,237,.06)',
progressTrackBg: 'rgba(124,58,237,.12)',
progressTrackBorder: 'rgba(124,58,237,.25)',
dotTodoDeep: '#faf5ff',
dotLitHi: 'rgba(255,255,255,.85)',
dotSoftHi: 'rgba(255,255,255,.55)',
sparkOp: 0.55,
},
}
type Theme = typeof THEMES.dark
// ── Data ──────────────────────────────────────────────────────────────────────
const TODAY_POSITION = 0.56
interface Milestone {
id: string
when: string
tick: string
title: { de: string; en: string }
short: { de: string; en: string }
body: { de: string; en: string }
bullets: { de: string[]; en: string[] }
tint: string
done: boolean
next?: boolean
}
const MILESTONES: Milestone[] = [
{
id: 'ihk',
when: 'Okt. 2025', tick: '10 · 25',
title: { de: 'Gründerzuschuss & IHK', en: 'Founder Grant & IHK' },
short: { de: 'Abstimmung mit Agentur für Arbeit und IHK Konstanz.', en: 'Coordination with Employment Agency and IHK Konstanz.' },
body: {
de: 'Seit Oktober 2025 Gründerzuschussantrag in Abstimmung mit der Agentur für Arbeit und der IHK Konstanz. Grundlage für die Unternehmensgründung.',
en: 'Since October 2025, founder grant application in coordination with the Employment Agency and IHK Konstanz. Foundation for company formation.',
},
bullets: {
de: ['Gründerzuschuss beantragt', 'Beratung IHK Konstanz', 'Businessplan finalisiert'],
en: ['Founder grant applied', 'IHK Konstanz advisory', 'Business plan finalized'],
},
tint: '#a78bfa', done: true,
},
{
id: 'brand',
when: '11. Nov. 2025', tick: '11 · 25',
title: { de: 'Markenanmeldung & Domains', en: 'Trademark Filing & Domains' },
short: { de: 'DPMA-Anmeldung BreakPilot + Domain-Portfolio.', en: 'DPMA filing BreakPilot + domain portfolio.' },
body: {
de: 'Markenanmeldung BreakPilot beim DPMA am 11.11.2025. Domain-Kauf breakpilot.com, .de, .ai und brakepilot.com, .de, .ai am 21.11.2025.',
en: 'BreakPilot trademark filed with DPMA on 11.11.2025. Domain purchase breakpilot.com, .de, .ai and brakepilot.com, .de, .ai on 21.11.2025.',
},
bullets: {
de: ['DPMA-Markenanmeldung 11.11.2025', 'Domains .com .de .ai gesichert', 'Typo-Domains (.brakepilot) gesichert'],
en: ['DPMA trademark filed 11.11.2025', 'Domains .com .de .ai secured', 'Typo domains (.brakepilot) secured'],
},
tint: '#a78bfa', done: true,
},
{
id: 'dev',
when: 'Jan. 2026', tick: '01 · 26',
title: { de: 'Plattform-Entwicklung gestartet', en: 'Platform Development Started' },
short: { de: '500.000+ Lines of Code, vollständige Architektur.', en: '500,000+ lines of code, full architecture.' },
body: {
de: 'Start der Plattform-Entwicklung mit 500.000+ Lines of Code. Vollständige Microservice-Architektur mit Go, Python und TypeScript.',
en: 'Platform development started with 500,000+ lines of code. Full microservice architecture with Go, Python and TypeScript.',
},
bullets: {
de: ['500K+ Lines of Code', 'Go + Python + TypeScript', 'Vollständige Architektur'],
en: ['500K+ lines of code', 'Go + Python + TypeScript', 'Full architecture'],
},
tint: '#c084fc', done: true,
},
{
id: 'dpma',
when: '27. Mär. 2026', tick: '03 · 26',
title: { de: 'Markeneintragung DPMA', en: 'DPMA Trademark Registration' },
short: { de: 'BreakPilot offiziell eingetragen.', en: 'BreakPilot officially registered.' },
body: {
de: 'Markeneintragung BreakPilot beim Deutschen Patent- und Markenamt (DPMA) am 27.03.2026.',
en: 'BreakPilot trademark registration at the German Patent and Trademark Office (DPMA) on 27.03.2026.',
},
bullets: {
de: ['DPMA-Eintragung 27.03.2026', 'Markenschutz Deutschland'],
en: ['DPMA registration 27.03.2026', 'Trademark protection Germany'],
},
tint: '#c084fc', done: true,
},
{
id: 'rag',
when: 'Apr. 2026', tick: '04 · 26',
title: { de: 'RAG mit 375+ Dokumenten', en: 'RAG with 375+ Documents' },
short: { de: 'EU + DACH Regularien indexiert.', en: 'EU + DACH regulations indexed.' },
body: {
de: '375+ Gesetze, Verordnungen, Leitlinien und Urteile in die RAG-Pipeline ingestiert. 25.000+ Prüfaspekte generiert.',
en: '375+ laws, regulations, guidelines and rulings ingested into the RAG pipeline. 25,000+ audit controls generated.',
},
bullets: {
de: ['375+ Dokumente im RAG', '25.000+ Prüfaspekte', 'EU + DACH Abdeckung'],
en: ['375+ documents in RAG', '25,000+ audit controls', 'EU + DACH coverage'],
},
tint: '#c084fc', done: true,
},
{
id: 'euipo',
when: '1. Mai 2026', tick: '05 · 26',
title: { de: 'Markenanmeldung EUIPO', en: 'EUIPO Trademark Filing' },
short: { de: 'EU-weiter Markenschutz beantragt.', en: 'EU-wide trademark protection filed.' },
body: {
de: 'Markenanmeldung BreakPilot beim EUIPO (Amt der Europäischen Union für geistiges Eigentum) am 01.05.2026 für EU-weiten Markenschutz.',
en: 'BreakPilot trademark filing with EUIPO (European Union Intellectual Property Office) on 01.05.2026 for EU-wide trademark protection.',
},
bullets: {
de: ['EUIPO-Anmeldung 01.05.2026', 'EU-weiter Markenschutz'],
en: ['EUIPO filing 01.05.2026', 'EU-wide trademark protection'],
},
tint: '#fbbf24', done: false, next: true,
},
{
id: 'gmbh',
when: 'Aug. 2026', tick: '08 · 26',
title: { de: 'GmbH-Gründung', en: 'GmbH Incorporation' },
short: { de: 'Breakpilot COMPLAI GmbH gegründet.', en: 'Breakpilot COMPLAI GmbH incorporated.' },
body: {
de: 'Gründung der Breakpilot COMPLAI GmbH im August 2026. Notartermin, Handelsregistereintrag, operative Aufnahme.',
en: 'Incorporation of Breakpilot COMPLAI GmbH in August 2026. Notary appointment, commercial register entry, start of operations.',
},
bullets: {
de: ['GmbH-Gründung August 2026', 'Handelsregistereintrag', 'Operativer Start'],
en: ['GmbH incorporation August 2026', 'Commercial register entry', 'Start of operations'],
},
tint: '#fbbf24', done: false,
},
{
id: 'customers',
when: 'Aug. 2026', tick: '08 · 26',
title: { de: '2 zahlende Kunden', en: '2 Paying Customers' },
short: { de: 'Erste Umsätze ab Gründung.', en: 'First revenue from incorporation.' },
body: {
de: 'Zwei zahlende Kunden ab August 2026 — Validierung des Produkts im Maschinenbau-Umfeld mit echten Compliance-Anforderungen.',
en: 'Two paying customers from August 2026 — product validation in manufacturing with real compliance requirements.',
},
bullets: {
de: ['2 zahlende Kunden', 'Maschinenbau-Validierung', 'Erste Umsätze'],
en: ['2 paying customers', 'Manufacturing validation', 'First revenue'],
},
tint: '#fbbf24', done: false,
},
{
id: 'beta',
when: 'Q3 2026', tick: 'Q3 · 26',
title: { de: 'Öffentliches Beta', en: 'Public Beta' },
short: { de: 'Beta-Launch mit ersten zahlenden Kunden.', en: 'Beta launch with first paying customers.' },
body: {
de: 'Öffentliches Beta-Release der Plattform. Erste zahlende Kunden aus dem Pilotprogramm gehen live.',
en: 'Public beta release of the platform. First paying customers from the pilot program go live.',
},
bullets: {
de: ['Public Beta verfügbar', 'Onboarding-Prozess live', 'Feedback-Loop etabliert'],
en: ['Public beta available', 'Onboarding process live', 'Feedback loop established'],
},
tint: '#f59e0b', done: false,
},
]
interface StatItem { k: { de: string; en: string }; v: string; tint: string }
const STATS: StatItem[] = [
{ k: { de: 'Gesetze & Dokumente im RAG', en: 'Laws & Docs in RAG' }, v: '385', tint: '#a78bfa' },
{ k: { de: 'Atomare Controls', en: 'Atomic Controls' }, v: '25.000+', tint: '#c084fc' },
{ k: { de: 'Compliance-Module', en: 'Compliance Modules' }, v: '12', tint: '#fbbf24' },
{ k: { de: 'Pilotkunden', en: 'Pilot Customers' }, v: '2', tint: '#f59e0b' },
{ k: { de: 'Lines of Code', en: 'Lines of Code' }, v: '500.000+', tint: '#8b5cf6' },
]
// ── Star Field ────────────────────────────────────────────────────────────────
function StarField() {
const stars = useMemo(() => {
let s = 77
const r = () => { s = (s * 9301 + 49297) % 233280; return s / 233280 }
return Array.from({ length: 95 }, () => ({ x: r() * 100, y: r() * 100, size: r() * 1.4 + 0.3, op: r() * 0.5 + 0.15 }))
}, [])
return (
<div style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>
{stars.map((st, i) => (
<div key={i} style={{
position: 'absolute', left: `${st.x}%`, top: `${st.y}%`,
width: st.size, height: st.size, borderRadius: '50%',
background: '#fff', opacity: st.op,
boxShadow: `0 0 ${st.size * 3}px rgba(180,160,255,.7)`,
}} />
))}
</div>
)
}
function SoftGrid({ t }: { t: Theme }) {
return (
<div style={{
position: 'absolute', inset: 0, pointerEvents: 'none',
backgroundImage: `radial-gradient(${t.accent20} 1px, transparent 1px)`,
backgroundSize: '28px 28px',
maskImage: 'radial-gradient(ellipse at center, #000 40%, transparent 85%)',
WebkitMaskImage: 'radial-gradient(ellipse at center, #000 40%, transparent 85%)',
opacity: 0.8,
}} />
)
}
// ── Timeline ──────────────────────────────────────────────────────────────────
interface MilestoneWithPos extends Milestone { x: number; row: 'top' | 'bottom' }
function Timeline({ onSelect, selectedId, t, de }: {
onSelect: (m: Milestone) => void
selectedId: string | null
t: Theme
de: boolean
}) {
const trackW = 1160
const innerPad = 120
const usableW = trackW - innerPad * 2
const positions = MILESTONES.map((_, i) => innerPad + (usableW * i) / (MILESTONES.length - 1))
const todayX = innerPad + usableW * TODAY_POSITION
const layout: MilestoneWithPos[] = MILESTONES.map((m, i) => ({
...m, x: positions[i],
row: i % 2 === 0 ? 'top' : 'bottom',
}))
const railColor = t.key === 'dark' ? '#a78bfa' : '#7c3aed'
return (
<div style={{ position: 'relative', width: trackW, height: 360, margin: '0 auto' }}>
<svg viewBox={`0 0 ${trackW} 360`} preserveAspectRatio="none"
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%' }}>
<defs>
<linearGradient id="msTrackBg" x1="0" x2="1">
<stop offset="0" stopColor={railColor} stopOpacity={t.key === 'dark' ? .18 : .28} />
<stop offset=".5" stopColor={railColor} stopOpacity={t.key === 'dark' ? .28 : .38} />
<stop offset="1" stopColor={railColor} stopOpacity={t.key === 'dark' ? .18 : .28} />
</linearGradient>
<filter id="msGlow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="3" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
{/* rail background */}
<line x1={innerPad} y1={180} x2={trackW - innerPad} y2={180}
stroke="url(#msTrackBg)" strokeWidth="2.5" />
{/* past progress */}
<line x1={innerPad} y1={180} x2={todayX} y2={180}
stroke={t.done} strokeWidth="3" opacity={t.key === 'dark' ? .85 : .9} />
{/* future dashed */}
<line x1={todayX} y1={180} x2={trackW - innerPad} y2={180}
stroke="#f59e0b" strokeWidth="1.75" strokeDasharray="4 5"
opacity={t.key === 'dark' ? .6 : .75}
style={{ animation: 'msFlow 1.8s linear infinite' }} />
{/* connector stubs */}
{layout.map((m) => (
<line key={m.id}
x1={m.x} y1={180}
x2={m.x} y2={m.row === 'top' ? 154 : 200}
stroke={m.done ? t.done : m.tint}
strokeOpacity={t.key === 'dark' ? (m.done ? .6 : .55) : (m.done ? .7 : .65)}
strokeWidth="1"
strokeDasharray={m.done ? '0' : '3 3'} />
))}
{/* HEUTE marker — circles only; pill is HTML below */}
<g transform={`translate(${todayX} 180)`}>
<circle r="14" fill={t.accent} opacity=".15" />
<circle r="9" fill={t.accent} opacity=".4">
<animate attributeName="r" values="9;14;9" dur="2s" repeatCount="indefinite" />
<animate attributeName="opacity" values=".4;.05;.4" dur="2s" repeatCount="indefinite" />
</circle>
<circle r="6" fill={t.heuteCore} stroke={t.accent} strokeWidth="2" filter="url(#msGlow)" />
</g>
</svg>
{/* HEUTE pill — HTML so it sits above milestone cards */}
<div style={{
position: 'absolute',
left: todayX - 30, top: 146,
width: 60, height: 18,
borderRadius: 9,
background: t.heutePillBg,
border: `1px solid ${t.accent}99`,
display: 'flex', alignItems: 'center', justifyContent: 'center',
zIndex: 10, pointerEvents: 'none',
...MONO, fontSize: 9.5, letterSpacing: 2.5, fontWeight: 700,
color: t.heuteText,
}}>HEUTE</div>
{layout.map((m) => (
<MilestoneNode key={m.id} m={m} t={t} de={de}
onClick={() => onSelect(m)}
active={selectedId === m.id} />
))}
</div>
)
}
function MilestoneNode({ m, onClick, active, t, de }: {
m: MilestoneWithPos; onClick: () => void; active: boolean; t: Theme; de: boolean
}) {
const [hover, setHover] = useState(false)
const lit = hover || active
const isTop = m.row === 'top'
const cardY = isTop ? 4 : 200
const nodeColor = m.done ? t.done : m.tint
const bgTopA = lit ? m.tint + t.cardTintTopH : m.tint + t.cardTintTop
const bgMidA = lit ? m.tint + t.cardTintMidH : m.tint + t.cardTintMid
const cardBg = `linear-gradient(180deg, ${bgTopA} 0%, ${bgMidA} 55%, ${t.cardBase}${lit ? t.cardBaseAH : t.cardBaseA})`
const badge = m.done ? (de ? 'erledigt' : 'done') : (m.next ? (de ? 'als nächstes' : 'next') : (de ? 'geplant' : 'plan'))
return (
<>
{/* dot */}
<div
onClick={onClick}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
style={{
position: 'absolute', left: m.x - 14, top: 180 - 14,
width: 28, height: 28, borderRadius: '50%',
background: m.done
? `radial-gradient(circle at 35% 30%, ${t.doneBright}, ${t.doneSolid} 60%, ${t.doneDeep})`
: `radial-gradient(circle at 35% 30%, ${m.tint}dd, ${m.tint}66 60%, ${t.dotTodoDeep})`,
border: `2px solid ${lit ? '#fff' : nodeColor}`,
boxShadow: lit
? `0 0 22px ${nodeColor}, 0 0 44px ${nodeColor}66, inset 0 1px 0 ${t.dotLitHi}`
: `0 0 10px ${nodeColor}88, inset 0 1px 0 ${t.dotSoftHi}`,
display: 'flex', alignItems: 'center', justifyContent: 'center',
color: '#fff', fontSize: 11, fontWeight: 700,
cursor: 'pointer', zIndex: 5,
transition: 'all .25s',
transform: lit ? 'scale(1.15)' : 'scale(1)',
}}>
{m.done ? '✓' : (m.next ? '◉' : '○')}
</div>
{/* card */}
<div
onClick={onClick}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
style={{
position: 'absolute', left: m.x - 112, top: cardY,
width: 224, height: 150, padding: '12px 14px',
borderRadius: 12,
background: cardBg,
border: `1px solid ${lit ? m.tint : m.tint + '55'}`,
boxShadow: lit ? t.cardShadowLift(m.tint) : t.cardShadowSoft,
cursor: 'pointer', zIndex: 4,
transition: 'all .25s',
transform: lit ? `translateY(${isTop ? -2 : 2}px)` : 'translateY(0)',
display: 'flex', flexDirection: 'column', gap: 6,
backdropFilter: t.key === 'light' ? 'blur(6px)' : 'none',
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span style={{
...MONO, fontSize: 10, letterSpacing: 1.5, fontWeight: 700,
color: m.done ? t.done : m.tint, textTransform: 'uppercase' as const,
}}>{m.tick}</span>
<span style={{ flex: 1, height: 1, background: `${m.tint}44` }} />
<span style={{
...MONO, fontSize: 9, letterSpacing: 2, fontWeight: 700,
color: m.done ? t.done : m.tint, textTransform: 'uppercase' as const, opacity: .85,
}}>{badge}</span>
</div>
<div style={{ fontSize: 13, fontWeight: 700, color: t.fg, letterSpacing: -0.2, lineHeight: 1.25 }}>
{de ? m.title.de : m.title.en}
</div>
<div style={{ fontSize: 10.5, lineHeight: 1.45, color: lit ? t.fgSoft : t.fgMuted, transition: 'color .25s' }}>
{de ? m.short.de : m.short.en}
</div>
<div style={{
marginTop: 'auto',
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
paddingTop: 6, borderTop: `1px dashed ${m.tint}44`,
}}>
<span style={{ fontSize: 10, color: t.fgFaint }}>{m.when}</span>
<span style={{
fontSize: 10, color: m.tint, fontWeight: 700,
opacity: lit ? 1 : 0.55,
transform: `translateX(${lit ? 0 : -4}px)`,
transition: 'all .25s',
}}>{de ? 'Details →' : 'Details →'}</span>
</div>
</div>
</>
)
}
// ── Stat Card ─────────────────────────────────────────────────────────────────
function StatCard({ item, t, de }: { item: StatItem; t: Theme; de: boolean }) {
const [hover, setHover] = useState(false)
const bgTop = hover ? item.tint + t.statTintTopH : item.tint + t.statTintTop
const bgMid = item.tint + t.statTintMid
return (
<div
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
style={{
position: 'relative', padding: '14px 18px', borderRadius: 12,
background: `linear-gradient(180deg, ${bgTop} 0%, ${bgMid} 60%, ${t.cardBase}${t.cardBaseA})`,
border: `1px solid ${hover ? item.tint : item.tint + '55'}`,
boxShadow: hover ? t.statShadowLift(item.tint) : t.statShadowSoft,
transform: hover ? 'translateY(-3px)' : 'translateY(0)',
transition: 'all .25s',
overflow: 'hidden',
backdropFilter: t.key === 'light' ? 'blur(6px)' : 'none',
}}>
<div style={{
position: 'absolute', right: 10, top: 10, width: 6, height: 6,
borderRadius: '50%', background: item.tint, opacity: .9,
boxShadow: `0 0 10px ${item.tint}`,
}} />
<div style={{ ...MONO, fontSize: 9.5, letterSpacing: 2, color: item.tint, textTransform: 'uppercase' as const, fontWeight: 700, marginBottom: 6 }}>
{de ? item.k.de : item.k.en}
</div>
<div style={{ fontSize: 32, fontWeight: 700, color: t.fg, letterSpacing: -0.8, lineHeight: 1 }}>
{item.v}
</div>
<svg viewBox="0 0 100 16" preserveAspectRatio="none"
style={{ width: '100%', height: 14, marginTop: 8, opacity: hover ? 1 : t.sparkOp, transition: 'opacity .25s' }}>
<defs>
<linearGradient id={`spark-${item.tint.replace('#', '')}`} x1="0" x2="1">
<stop offset="0" stopColor={item.tint} stopOpacity="0" />
<stop offset=".5" stopColor={item.tint} stopOpacity=".9" />
<stop offset="1" stopColor={item.tint} stopOpacity="0" />
</linearGradient>
</defs>
<path d="M 0 10 L 15 8 L 30 11 L 48 6 L 62 9 L 78 4 L 100 2"
stroke={`url(#spark-${item.tint.replace('#', '')})`} strokeWidth="1.5" fill="none" />
</svg>
</div>
)
}
// ── Detail modal ──────────────────────────────────────────────────────────────
function DetailModal({ item, onClose, t, de }: {
item: Milestone | null; onClose: () => void; t: Theme; de: boolean
}) {
useEffect(() => {
if (!item) return
const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose() }
window.addEventListener('keydown', onKey)
return () => window.removeEventListener('keydown', onKey)
}, [item, onClose])
if (!item) return null
const tint = item.tint
const badge = item.done
? (de ? 'ABGESCHLOSSEN' : 'COMPLETED')
: (item.next ? (de ? 'ALS NÄCHSTES' : 'NEXT UP') : (de ? 'GEPLANT' : 'PLANNED'))
const badgeColor = item.done ? t.done : tint
return (
<div onClick={onClose} style={{
position: 'absolute', inset: 0, zIndex: 50,
background: t.modalScrim, backdropFilter: 'blur(8px)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
animation: 'msFadeIn .2s ease-out',
}}>
<div onClick={e => e.stopPropagation()} style={{
width: 580, maxWidth: '88%',
background: `linear-gradient(180deg, ${tint}22 0%, ${t.modalBgMid} 50%, ${t.modalBgLow} 100%)`,
border: `1px solid ${tint}77`,
borderRadius: 16,
boxShadow: t.modalShadow(tint),
padding: '24px 28px', color: t.fg,
animation: 'msScaleIn .22s ease-out',
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 16 }}>
<div style={{
width: 42, height: 42, borderRadius: 11,
background: `linear-gradient(135deg, ${tint}66, ${tint}22)`,
border: `1px solid ${tint}`,
display: 'flex', alignItems: 'center', justifyContent: 'center',
color: t.key === 'light' ? tint : '#fff', fontSize: 17, fontWeight: 700,
boxShadow: `0 0 20px ${tint}66`,
}}>{item.done ? '✓' : (item.next ? '◉' : '○')}</div>
<div style={{ flex: 1 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 4 }}>
<span style={{
...MONO, fontSize: 9.5, letterSpacing: 2.5, color: badgeColor,
textTransform: 'uppercase' as const, fontWeight: 700,
padding: '2px 8px', borderRadius: 4,
background: `${badgeColor}22`, border: `1px solid ${badgeColor}66`,
}}>{badge}</span>
<span style={{ ...MONO, fontSize: 10, color: t.fgFaint }}>{item.when}</span>
</div>
<div style={{ fontSize: 20, fontWeight: 700, color: t.fg, letterSpacing: -0.3 }}>
{de ? item.title.de : item.title.en}
</div>
</div>
<button onClick={onClose} style={{
background: 'transparent', border: `1px solid ${tint}66`, color: t.fg,
width: 32, height: 32, borderRadius: 8, cursor: 'pointer', fontSize: 14,
}}></button>
</div>
<div style={{ fontSize: 13, lineHeight: 1.6, color: t.fgSoft, marginBottom: 16 }}>
{de ? item.body.de : item.body.en}
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
{(de ? item.bullets.de : item.bullets.en).map((b, i) => (
<div key={i} style={{
display: 'flex', alignItems: 'flex-start', gap: 10,
padding: '9px 13px', borderRadius: 8,
background: t.bulletBg, border: `1px solid ${tint}44`,
}}>
<span style={{ color: item.done ? t.done : tint, fontSize: 12, marginTop: 1 }}>
{item.done ? '✓' : '▸'}
</span>
<span style={{ fontSize: 12, lineHeight: 1.5, color: t.fgSoft }}>{b}</span>
</div>
))}
</div>
</div>
</div>
)
}
// ── Inner slide (fixed 1280×680) ──────────────────────────────────────────────
function MilestonesInner({ t, de, sel, setSel }: {
t: Theme; de: boolean
sel: Milestone | null
setSel: (m: Milestone | null) => void
}) {
const doneCnt = useMemo(() => MILESTONES.filter(m => m.done).length, [])
const total = MILESTONES.length
return (
<div style={{
position: 'relative', width: 1280, height: 600, overflow: 'hidden',
background: t.bg, color: t.fg,
fontFamily: '"Inter", system-ui, sans-serif', WebkitFontSmoothing: 'antialiased',
}}>
{/* Ambient glow */}
<div style={{
position: 'absolute', top: -120, left: '50%', transform: 'translateX(-50%)',
width: 800, height: 500, borderRadius: '50%',
background: t.ambient, filter: 'blur(50px)', pointerEvents: 'none',
}} />
{t.stars ? <StarField /> : <SoftGrid t={t} />}
{/* Progress indicator */}
<div style={{
position: 'absolute', top: 36, right: 52, display: 'flex', alignItems: 'center', gap: 10, zIndex: 3,
}}>
<div style={{ ...MONO, fontSize: 10, letterSpacing: 2, color: t.fgMuted, textTransform: 'uppercase' as const, fontWeight: 700 }}>
{de ? 'Fortschritt' : 'Progress'}
</div>
<div style={{
width: 120, height: 6, background: t.progressTrackBg, borderRadius: 3, overflow: 'hidden',
border: `1px solid ${t.progressTrackBorder}`,
}}>
<div style={{
width: `${(doneCnt / total) * 100}%`, height: '100%',
background: `linear-gradient(90deg, ${t.done}, ${t.accent})`,
boxShadow: `0 0 12px ${t.done}99`,
}} />
</div>
<div style={{ ...MONO, fontSize: 11, color: t.fg, fontWeight: 700 }}>
<span style={{ color: t.done }}>{doneCnt}</span>
<span style={{ color: t.fgWhisper }}> / {total}</span>
</div>
</div>
{/* Tip */}
<div style={{
position: 'absolute', top: 36, left: 52, ...MONO, fontSize: 10,
letterSpacing: 2, color: t.fgGhost, textTransform: 'uppercase' as const, fontWeight: 700,
display: 'flex', alignItems: 'center', gap: 8, zIndex: 3,
}}>
<span>{de ? 'Tipp:' : 'Tip:'}</span>
<span style={{ color: t.accent70 }}>{de ? 'Klick auf einen Meilenstein' : 'Click any milestone'}</span>
</div>
{/* Timeline */}
<div style={{ position: 'relative', marginTop: 68 }}>
<Timeline onSelect={setSel} selectedId={sel?.id ?? null} t={t} de={de} />
</div>
{/* Stats */}
<div style={{
position: 'absolute', left: 40, right: 40, bottom: 36,
display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: 14,
}}>
{STATS.map(s => <StatCard key={s.tint} item={s} t={t} de={de} />)}
</div>
{/* Footer */}
<div style={{
position: 'absolute', left: 0, right: 0, bottom: 14, textAlign: 'center',
...MONO, fontSize: 9, letterSpacing: 3, color: t.accent40,
textTransform: 'uppercase' as const, fontWeight: 700,
}}>
{de ? 'Stand heute · live-Metriken aus der Plattform' : 'As of today · live metrics from the platform'}
</div>
<DetailModal item={sel} onClose={() => setSel(null)} t={t} de={de} />
</div>
)
}
// ── Main slide ────────────────────────────────────────────────────────────────
const INNER_W = 1280
const INNER_H = 600
export default function MilestonesSlide({ lang }: MilestonesSlideProps) {
const de = lang === 'de'
const isLight = useIsLight()
const t = isLight ? THEMES.light : THEMES.dark
const [sel, setSel] = useState<Milestone | null>(null)
const [scale, setScale] = useState(1)
const containerRef = useRef<HTMLDivElement>(null)
const calcScale = useCallback(() => {
if (containerRef.current) {
const w = containerRef.current.offsetWidth
setScale(Math.min(w / INNER_W, 1))
}
}, [])
useEffect(() => {
calcScale()
const obs = new ResizeObserver(calcScale)
if (containerRef.current) obs.observe(containerRef.current)
return () => obs.disconnect()
}, [calcScale])
return (
<div>
<style>{CSS_KF}</style>
<FadeInView className="text-center mb-1">
<h2 className="text-5xl md:text-6xl font-bold mb-1">
<GradientText>{de ? 'Meilensteine' : 'Milestones'}</GradientText>
</h2>
<p className="text-lg text-white/50 max-w-2xl mx-auto">
{de ? 'Von der Idee zur GmbH — was wir bereits erreicht haben' : 'From idea to GmbH — what we have already achieved'}
</p>
</FadeInView>
<FadeInView delay={0.1}>
<div
ref={containerRef}
style={{
position: 'relative',
width: '100%',
height: INNER_H * scale,
overflow: 'hidden',
borderRadius: 16,
transform: 'scale(1.12)',
transformOrigin: 'top center',
marginBottom: -40,
}}
>
<div style={{
position: 'absolute', top: 0, left: 0,
width: INNER_W, height: INNER_H,
transform: `scale(${scale})`,
transformOrigin: 'top left',
}}>
<MilestonesInner t={t} de={de} sel={sel} setSel={setSel} />
</div>
</div>
</FadeInView>
</div>
)
}
+21 -11
View File
@@ -25,7 +25,7 @@ interface ProblemCardData {
sources: SourceInfo[]
}
// Quellenangaben fuer jede Behauptung
// Quellenangaben für jede Behauptung
const cardSources: ProblemCardData[] = [
{
// KI-Dilemma: Maschinenbauer wollen KI, aber nicht US-SaaS
@@ -34,7 +34,7 @@ const cardSources: ProblemCardData[] = [
name: 'Bitkom Cloud Monitor 2024 — Industrieunternehmen',
url: 'https://www.bitkom.org/Themen/Datenschutz-Sicherheit/Cloud-Monitor',
date: '2024',
excerpt_de: 'Laut Bitkom Cloud Monitor lehnen 64% der deutschen Industrieunternehmen US-Cloud-Dienste fuer sensible Daten ab. Im Maschinenbau liegt die Ablehnung bei ueber 70%. Unternehmen wollen KI nutzen, aber nicht auf Kosten ihrer Datensouveraenitaet.',
excerpt_de: 'Laut Bitkom Cloud Monitor lehnen 64% der deutschen Industrieunternehmen US-Cloud-Dienste für sensible Daten ab. Im Maschinenbau liegt die Ablehnung bei über 70%. Unternehmen wollen KI nutzen, aber nicht auf Kosten ihrer Datensouveraenitaet.',
excerpt_en: 'According to Bitkom Cloud Monitor, 64% of German industrial companies reject US cloud services for sensitive data. In machine manufacturing, rejection exceeds 70%. Companies want AI but not at the cost of their data sovereignty.',
},
{
@@ -53,7 +53,7 @@ const cardSources: ProblemCardData[] = [
name: 'Schrems II — EuGH C-311/18',
url: 'https://curia.europa.eu/juris/liste.jsf?num=C-311/18',
date: '2020',
excerpt_de: 'Der EuGH erklaerte das EU-US Privacy Shield fuer ungueltig. US-Unternehmen unterliegen dem CLOUD Act und dem FISA 702 — auch fuer Daten auf europaeischen Servern. Selbst EU-Rechenzentren von AWS, Google und Microsoft bieten keinen Schutz vor US-Zugriff.',
excerpt_de: 'Der EuGH erklaerte das EU-US Privacy Shield für ungueltig. US-Unternehmen unterliegen dem CLOUD Act und dem FISA 702 — auch für Daten auf europaeischen Servern. Selbst EU-Rechenzentren von AWS, Google und Microsoft bieten keinen Schutz vor US-Zugriff.',
excerpt_en: 'The CJEU invalidated the EU-US Privacy Shield. US companies are subject to the CLOUD Act and FISA 702 — even for data on European servers. Even EU data centers of AWS, Google and Microsoft offer no protection from US access.',
},
],
@@ -65,14 +65,14 @@ const cardSources: ProblemCardData[] = [
name: 'VDMA — Compliance-Kosten im Maschinenbau',
url: 'https://www.vdma.org/',
date: '2024',
excerpt_de: 'Externe Pentests kosten 15.000-40.000 EUR pro Durchlauf, CE-Software-Risikobeurteilungen 10.000-25.000 EUR. Diese Pruefungen erfolgen einmal jaehrlich und decken nur eine Momentaufnahme ab. KMU mit 10-500 Mitarbeitern koennen sich weder Personal noch Budget fuer kontinuierliche Compliance leisten.',
excerpt_de: 'Externe Pentests kosten 15.000-40.000 EUR pro Durchlauf, CE-Software-Risikobeurteilungen 10.000-25.000 EUR. Diese Prüfungen erfolgen einmal jaehrlich und decken nur eine Momentaufnahme ab. KMU mit 10-500 Mitarbeitern können sich weder Personal noch Budget für kontinuierliche Compliance leisten.',
excerpt_en: 'External pentests cost EUR 15,000-40,000 per run, CE software risk assessments EUR 10,000-25,000. These audits occur annually, covering only a snapshot. SMEs with 10-500 employees can afford neither staff nor budget for continuous compliance.',
},
{
name: 'Compliance-Markt: Top-10 Anbieter >$1,1 Mrd. Umsatz',
url: 'https://sacra.com/c/vanta/',
date: '2025',
excerpt_de: 'Vanta ($220M ARR, $4,15 Mrd. Bewertung), Drata ($100M), OneTrust ($500M+), DataGuard (€52M). Der Markt ist validiert — aber keiner dieser Anbieter kombiniert Code-Security mit Compliance fuer den Maschinenbau.',
excerpt_de: 'Vanta ($220M ARR, $4,15 Mrd. Bewertung), Drata ($100M), OneTrust ($500M+), DataGuard (€52M). Der Markt ist validiert — aber keiner dieser Anbieter kombiniert Code-Security mit Compliance für den Maschinenbau.',
excerpt_en: 'Vanta ($220M ARR, $4.15B valuation), Drata ($100M), OneTrust ($500M+), DataGuard (€52M). The market is validated — but none of these providers combine code security with compliance for manufacturing.',
},
],
@@ -194,19 +194,29 @@ export default function ProblemSlide({ lang }: ProblemSlideProps) {
<p className="text-[10px] text-indigo-400/60 group-hover:text-indigo-400 transition-colors">
{sourceCount} {lang === 'de' ? (sourceCount === 1 ? 'Quelle' : 'Quellen') : (sourceCount === 1 ? 'source' : 'sources')}
{' · '}
{lang === 'de' ? 'Klicken fuer Details' : 'Click for details'}
{lang === 'de' ? 'Klicken für Details' : 'Click for details'}
</p>
</GlassCard>
)
})}
</div>
<FadeInView delay={0.8} className="max-w-3xl mx-auto">
<blockquote className="text-center">
<p className="text-lg md:text-xl text-white/70 italic leading-relaxed">
&ldquo;{i.problem.quote}&rdquo;
<FadeInView delay={0.8} className="max-w-4xl mx-auto">
<div className="bg-gradient-to-r from-amber-500/10 to-indigo-500/10 border border-amber-500/20 rounded-xl px-6 py-5">
<div className="flex items-center gap-4">
<div className="w-16 h-16 rounded-2xl bg-gradient-to-br from-amber-500 to-orange-600 flex items-center justify-center shrink-0 shadow-lg">
<Shield className="w-8 h-8 text-white" />
</div>
<div>
<h3 className="text-xl font-bold text-amber-300 mb-1">
{lang === 'de' ? 'Die Konsequenz' : 'The Consequence'}
</h3>
<p className="text-sm text-white/50 leading-relaxed">
{i.problem.quote}
</p>
</blockquote>
</div>
</div>
</div>
</FadeInView>
{/* Source Modals */}
+19 -50
View File
@@ -24,9 +24,9 @@ const MODULES = [
{ icon: UserCheck, color: '#14b8a6', de: 'Consent Management', en: 'Consent Management', descDe: 'Einwilligungen, Cookie-Banner, Widerruf', descEn: 'Consent, cookie banner, withdrawal' },
{ icon: AlertTriangle, color: '#f59e0b', de: 'Notfallpläne', en: 'Incident Response', descDe: 'Datenschutzvorfälle, Meldepflichten, Eskalation', descEn: 'Data breaches, reporting obligations, escalation' },
{ icon: Brain, color: '#a855f7', de: 'Compliance LLM', en: 'Compliance LLM', descDe: 'GPT für Text und Audio — sicher in der EU', descEn: 'GPT for text and audio — securely in EU' },
{ icon: Shield, color: '#8b5cf6', de: 'Cookie-Generator', en: 'Cookie Generator', descDe: 'Cookie-Banner, Consent-Konfiguration', descEn: 'Cookie banner, consent configuration' },
{ icon: Shield, color: '#8b5cf6', de: 'Tender Matching', en: 'Tender Matching', descDe: 'Kundenanfragen (RFQ) gegen Codebase prüfen', descEn: 'Verify customer RFQs against codebase' },
{ icon: GraduationCap, color: '#ec4899', de: 'Academy', en: 'Academy', descDe: 'Online-Schulungen für GF und Mitarbeiter', descEn: 'Online training for management and employees' },
{ icon: Puzzle, color: '#0ea5e9', de: 'Integration in Kundenprozesse', en: 'Process Integration', descDe: 'Ticketsysteme, Workflows', descEn: 'Ticket systems, workflows' },
{ icon: Puzzle, color: '#0ea5e9', de: 'Compliance Optimizer', en: 'Compliance Optimizer', descDe: 'Maximale KI-Nutzung im regulatorischen Rahmen', descEn: 'Maximum AI usage within regulatory limits' },
{ icon: CheckCircle2, color: '#22c55e', de: 'Sichere Kommunikation', en: 'Secure Communication', descDe: 'Chat + Video mit AI Notetaker', descEn: 'Chat + video with AI notetaker' },
]
@@ -58,68 +58,37 @@ export default function ProductSlide({ lang }: ProductSlideProps) {
<GlassCard key={idx} delay={0.1 + idx * 0.05} hover className="p-3 text-center">
<Icon className="w-5 h-5 mx-auto mb-2" style={{ color: mod.color }} />
<p className="text-xs font-bold text-white mb-1">{de ? mod.de : mod.en}</p>
<p className="text-[10px] text-white/40 leading-tight">{de ? mod.descDe : mod.descEn}</p>
<p className="text-xs text-white/40 leading-tight">{de ? mod.descDe : mod.descEn}</p>
</GlassCard>
)
})}
</div>
{/* Pricing + Deployment */}
{/* Deployment Options — 2 cards side by side */}
<div className="grid md:grid-cols-2 gap-4">
{/* Pricing */}
<FadeInView delay={0.6}>
<GlassCard hover={false} className="p-4">
<h3 className="text-xs font-bold text-indigo-400 uppercase tracking-wider mb-3">{i.product.pricingTitle}</h3>
<p className="text-[10px] text-white/40 mb-3">{i.product.pricingSubtitle}</p>
<div className="space-y-2">
{PRICING_TIERS.map((tier, idx) => (
<div
key={idx}
className={`flex justify-between items-center p-2.5 rounded-xl ${
tier.highlight ? 'bg-indigo-500/15 border border-indigo-500/30' : 'bg-white/[0.04]'
}`}
>
<div>
<span className="text-xs text-white/70 font-medium">{tier.employees}</span>
<span className="text-[10px] text-white/40 ml-1">{de ? 'Mitarbeiter' : 'employees'}</span>
<GlassCard hover={false} className="p-4 h-full border-t-2 border-t-blue-500">
<div className="flex items-center gap-2 mb-2">
<Cloud className="w-5 h-5 text-blue-400" />
<h3 className="text-sm font-bold text-blue-400">{i.product.cloud}</h3>
</div>
<div className="text-right">
<span className={`text-xs font-bold ${tier.highlight ? 'text-indigo-300' : 'text-white/70'}`}>
{de ? tier.priceDe : tier.priceEn}
</span>
{tier.noteDe && (
<p className="text-[8px] text-white/30">{de ? tier.noteDe : tier.noteEn}</p>
)}
</div>
</div>
))}
<p className="text-sm text-white/50 leading-relaxed mb-3">{i.product.cloudDesc}</p>
<div className="flex gap-2">
<span className="text-xs bg-blue-500/15 text-blue-300 px-2 py-0.5 rounded-full">BSI DE</span>
<span className="text-xs bg-blue-500/15 text-blue-300 px-2 py-0.5 rounded-full">{de ? 'Fix oder flexibel' : 'Fixed or flexible'}</span>
</div>
</GlassCard>
</FadeInView>
{/* Deployment Options */}
<FadeInView delay={0.7}>
<GlassCard hover={false} className="p-4">
<div className="space-y-4">
<div>
<div className="flex items-center gap-2 mb-1">
<Cloud className="w-4 h-4 text-blue-400" />
<h3 className="text-xs font-bold text-blue-400 uppercase tracking-wider">{i.product.cloud}</h3>
</div>
<p className="text-[10px] text-white/50 leading-relaxed">{i.product.cloudDesc}</p>
<div className="flex gap-2 mt-2">
<span className="text-[9px] bg-blue-500/15 text-blue-300 px-2 py-0.5 rounded-full">BSI DE</span>
<span className="text-[9px] bg-blue-500/15 text-blue-300 px-2 py-0.5 rounded-full">OVH FR</span>
<span className="text-[9px] bg-blue-500/15 text-blue-300 px-2 py-0.5 rounded-full">{de ? 'Fix oder flexibel' : 'Fixed or flexible'}</span>
</div>
</div>
<div className="border-t border-white/10 pt-3">
<div className="flex items-center gap-2 mb-1">
<HardDrive className="w-4 h-4 text-white/40" />
<h3 className="text-xs font-bold text-white/40 uppercase tracking-wider">{i.product.privacy}</h3>
</div>
<p className="text-[10px] text-white/40 leading-relaxed">{i.product.privacyDesc}</p>
<GlassCard hover={false} className="p-4 h-full border-t-2 border-t-emerald-500">
<div className="flex items-center gap-2 mb-2">
<HardDrive className="w-5 h-5 text-emerald-400" />
<h3 className="text-sm font-bold text-emerald-400">{i.product.privacy}</h3>
</div>
<p className="text-sm text-white/50 leading-relaxed mb-3">{i.product.privacyDesc}</p>
<div className="flex gap-2">
<span className="text-xs bg-emerald-500/15 text-emerald-300 px-2 py-0.5 rounded-full">{de ? 'Geplant, optional' : 'Planned, optional'}</span>
</div>
</GlassCard>
</FadeInView>
@@ -6,74 +6,105 @@ import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
import GlassCard from '../ui/GlassCard'
import KPICard from '../ui/KPICard'
import {
Shield, Lock, Brain, Globe, Package, Landmark, Heart, ShoppingCart,
Activity, Cpu, Bot, Radio, Monitor, Building2, Cog
} from 'lucide-react'
interface RegulatoryLandscapeSlideProps {
lang: Language
}
// Regulation categories with their color
const CATEGORIES = [
{ id: 'privacy', color: '#6366f1', icon: Shield },
{ id: 'cyber', color: '#ef4444', icon: Lock },
{ id: 'ai', color: '#a855f7', icon: Brain },
{ id: 'markets', color: '#22c55e', icon: Globe },
{ id: 'product', color: '#f97316', icon: Package },
{ id: 'finance', color: '#10b981', icon: Landmark },
{ id: 'health', color: '#ec4899', icon: Heart },
{ id: 'consumer', color: '#f59e0b', icon: ShoppingCart },
// Key EU regulations as columns — the ones investors care about
const KEY_REGULATIONS = [
{ id: 'GDPR', label: 'DSGVO', color: '#6366f1' },
{ id: 'AI_ACT', label: 'AI Act', color: '#a855f7' },
{ id: 'NIS2', label: 'NIS2', color: '#ef4444' },
{ id: 'CRA', label: 'CRA', color: '#f97316' },
{ id: 'MACHINERY_REG', label: 'Masch.-VO', color: '#22c55e' },
{ id: 'DATA_ACT', label: 'Data Act', color: '#06b6d4' },
{ id: 'BATTERIE_VO', label: 'Batt.-VO', color: '#f59e0b' },
]
// Industry → which categories apply (synced with INDUSTRY_REGULATION_MAP in breakpilot-lehrer)
const INDUSTRY_MATRIX: { id: string; icon: typeof Shield; categories: string[]; regCount: number }[] = [
{ id: 'allIndustries', icon: Building2, categories: ['privacy'], regCount: 6 },
{ id: 'maschinenbau', icon: Cog, categories: ['privacy', 'cyber', 'ai', 'product', 'consumer'], regCount: 15 },
{ id: 'health', icon: Heart, categories: ['privacy', 'cyber', 'ai', 'product', 'health'], regCount: 12 },
{ id: 'finance', icon: Landmark, categories: ['privacy', 'cyber', 'ai', 'markets', 'finance'], regCount: 15 },
{ id: 'ecommerce', icon: ShoppingCart, categories: ['privacy', 'markets', 'product', 'finance', 'consumer'], regCount: 25 },
{ id: 'tech', icon: Cpu, categories: ['privacy', 'cyber', 'ai', 'markets'], regCount: 14 },
{ id: 'iot', icon: Activity, categories: ['privacy', 'cyber', 'ai', 'product', 'consumer'], regCount: 13 },
{ id: 'ai', icon: Bot, categories: ['privacy', 'cyber', 'ai', 'product', 'markets'], regCount: 9 },
{ id: 'kritis', icon: Lock, categories: ['privacy', 'cyber', 'ai', 'finance', 'markets'], regCount: 9 },
{ id: 'media', icon: Monitor, categories: ['privacy', 'markets', 'ai'], regCount: 9 },
{ id: 'public', icon: Radio, categories: ['privacy', 'cyber', 'ai', 'markets', 'health'], regCount: 10 },
// 10 real VDMA/VDA/BDI industry sectors with regulation applicability
// 380+ documents in RAG pipeline (EU + DACH regulations, standards, rulings)
const INDUSTRIES: { id: string; de: string; en: string; regs: string[]; totalDocs: number }[] = [
{
id: 'automotive',
de: 'Automobilindustrie',
en: 'Automotive',
regs: ['GDPR', 'AI_ACT', 'NIS2', 'CRA', 'MACHINERY_REG', 'DATA_ACT', 'BATTERIE_VO'],
totalDocs: 263,
},
{
id: 'maschinenbau',
de: 'Maschinen- & Anlagenbau',
en: 'Machinery & Plant Eng.',
regs: ['GDPR', 'AI_ACT', 'NIS2', 'CRA', 'MACHINERY_REG', 'DATA_ACT'],
totalDocs: 266,
},
{
id: 'elektrotechnik',
de: 'Elektro- & Digitalindustrie',
en: 'Electrical & Digital',
regs: ['GDPR', 'AI_ACT', 'NIS2', 'CRA', 'MACHINERY_REG', 'DATA_ACT', 'BATTERIE_VO'],
totalDocs: 281,
},
{
id: 'chemie',
de: 'Chemie- & Prozessindustrie',
en: 'Chemicals & Process',
regs: ['GDPR', 'AI_ACT', 'NIS2', 'CRA', 'DATA_ACT'],
totalDocs: 250,
},
{
id: 'metall',
de: 'Metallindustrie',
en: 'Metal Industry',
regs: ['GDPR', 'AI_ACT', 'NIS2', 'CRA', 'MACHINERY_REG', 'DATA_ACT'],
totalDocs: 246,
},
{
id: 'energie',
de: 'Energie & Versorgung',
en: 'Energy & Utilities',
regs: ['GDPR', 'AI_ACT', 'NIS2', 'CRA', 'DATA_ACT', 'BATTERIE_VO'],
totalDocs: 256,
},
{
id: 'transport',
de: 'Transport & Logistik',
en: 'Transport & Logistics',
regs: ['GDPR', 'AI_ACT', 'NIS2', 'CRA', 'DATA_ACT'],
totalDocs: 256,
},
{
id: 'handel',
de: 'Handel',
en: 'Retail & Commerce',
regs: ['GDPR', 'AI_ACT', 'NIS2', 'CRA', 'DATA_ACT'],
totalDocs: 271,
},
{
id: 'konsumgueter',
de: 'Konsumgüter & Lebensmittel',
en: 'Consumer Goods & Food',
regs: ['GDPR', 'AI_ACT', 'NIS2', 'CRA', 'DATA_ACT', 'BATTERIE_VO'],
totalDocs: 265,
},
{
id: 'bau',
de: 'Bauwirtschaft',
en: 'Construction',
regs: ['GDPR', 'AI_ACT', 'NIS2', 'CRA', 'MACHINERY_REG', 'DATA_ACT'],
totalDocs: 245,
},
]
export default function RegulatoryLandscapeSlide({ lang }: RegulatoryLandscapeSlideProps) {
const i = t(lang)
const rl = i.regulatoryLandscape
const categoryLabels: Record<string, string> = {
privacy: rl.categoryPrivacy,
cyber: rl.categoryCyber,
ai: rl.categoryAI,
markets: rl.categoryMarkets,
product: rl.categoryProduct,
finance: rl.categoryFinance,
health: rl.categoryHealth,
consumer: rl.categoryConsumer,
}
const industryLabels: Record<string, string> = {
allIndustries: rl.allIndustries,
maschinenbau: rl.maschinenbau,
health: rl.health,
finance: rl.finance,
ecommerce: rl.ecommerce,
tech: rl.tech,
iot: rl.iot,
ai: rl.ai,
kritis: rl.kritis,
media: rl.media,
public: rl.public,
}
const de = lang === 'de'
return (
<div className="max-w-6xl mx-auto">
<FadeInView className="text-center mb-6">
<FadeInView className="text-center mb-5">
<h2 className="text-3xl md:text-5xl font-bold mb-2">
<GradientText>{rl.title}</GradientText>
</h2>
@@ -81,68 +112,71 @@ export default function RegulatoryLandscapeSlide({ lang }: RegulatoryLandscapeSl
</FadeInView>
{/* KPI Row */}
<div className="grid grid-cols-3 gap-3 mb-6">
<KPICard label={rl.controls} value={25000} suffix="+" color="#6366f1" delay={0.1} />
<KPICard label={rl.regulations} value={110} color="#a78bfa" delay={0.2} />
<KPICard label={rl.industries} value={10} color="#34d399" delay={0.4} />
<div className="grid grid-cols-4 gap-3 mb-5">
<KPICard label={de ? 'Gesetze & Dokumente im RAG' : 'Laws & Documents in RAG'} value="380+" color="#6366f1" delay={0.1} />
<KPICard label={de ? 'Gelten für alle Branchen' : 'Apply to All Industries'} value={244} color="#a78bfa" delay={0.2} />
<KPICard label={de ? 'Branchenspezifische Gesetze' : 'Industry-specific Laws'} value={65} color="#f97316" delay={0.3} />
<KPICard label={de ? 'Abgedeckte Branchen' : 'Covered Industries'} value={10} color="#34d399" delay={0.4} />
</div>
{/* Matrix */}
<FadeInView delay={0.5}>
<GlassCard hover={false} className="p-4 overflow-x-auto">
{/* Category Legend */}
<div className="flex flex-wrap gap-3 mb-4 justify-center">
{CATEGORIES.map((cat) => (
<div key={cat.id} className="flex items-center gap-1.5">
<div className="w-2.5 h-2.5 rounded-full" style={{ backgroundColor: cat.color }} />
<span className="text-[10px] text-white/50">{categoryLabels[cat.id]}</span>
<div className="space-y-1">
{/* Header rows — staggered for space */}
<div className="grid items-end gap-1" style={{ gridTemplateColumns: '180px repeat(7, 1fr) 70px' }}>
<div className="text-[9px] text-white/70 uppercase tracking-wider pl-1 font-semibold">
{de ? 'Branche' : 'Industry'}
</div>
{KEY_REGULATIONS.map((reg, idx) => (
<div key={reg.id} className="text-center">
{idx % 2 === 0 ? (
<span className="text-[8px] font-semibold uppercase tracking-wider" style={{ color: reg.color }}>
{reg.label}
</span>
) : null}
</div>
))}
<div className="text-[8px] text-indigo-400 text-center font-semibold uppercase tracking-wider">
{de ? 'Gesetze gesamt' : 'Total Laws'}
</div>
{/* Matrix Grid */}
<div className="space-y-1.5">
{/* Header row */}
<div className="grid items-center gap-1" style={{ gridTemplateColumns: '140px repeat(8, 1fr) 50px' }}>
<div className="text-[9px] text-white/30 uppercase tracking-wider pl-1">
{lang === 'de' ? 'Branche' : 'Industry'}
</div>
{CATEGORIES.map((cat) => {
const CatIcon = cat.icon
return (
<div key={cat.id} className="flex justify-center">
<CatIcon className="w-3.5 h-3.5 opacity-50" style={{ color: cat.color }} />
<div className="grid items-start gap-1" style={{ gridTemplateColumns: '180px repeat(7, 1fr) 70px' }}>
<div />
{KEY_REGULATIONS.map((reg, idx) => (
<div key={reg.id} className="text-center">
{idx % 2 === 1 ? (
<span className="text-[8px] font-semibold uppercase tracking-wider" style={{ color: reg.color }}>
{reg.label}
</span>
) : null}
</div>
)
})}
<div className="text-[9px] text-white/30 text-center">#</div>
))}
<div />
</div>
{/* Industry rows */}
{INDUSTRY_MATRIX.map((industry, idx) => {
const Icon = industry.icon
return (
{INDUSTRIES.map((industry) => (
<div
key={industry.id}
className="grid items-center gap-1 py-1.5 rounded-lg hover:bg-white/[0.04] transition-colors"
style={{ gridTemplateColumns: '140px repeat(8, 1fr) 50px' }}
style={{ gridTemplateColumns: '180px repeat(7, 1fr) 70px' }}
>
<div className="flex items-center gap-2 pl-1">
<Icon className="w-3.5 h-3.5 text-white/40" />
<span className="text-[11px] text-white/70 font-medium truncate">
{industryLabels[industry.id]}
{de ? industry.de : industry.en}
</span>
</div>
{CATEGORIES.map((cat) => {
const applies = industry.categories.includes(cat.id)
{KEY_REGULATIONS.map((reg) => {
const applies = industry.regs.includes(reg.id)
return (
<div key={cat.id} className="flex justify-center">
<div key={reg.id} className="flex justify-center">
{applies ? (
<div
className="w-4 h-4 rounded-full flex items-center justify-center"
style={{ backgroundColor: `${cat.color}20` }}
style={{ backgroundColor: `${reg.color}20` }}
>
<div className="w-2 h-2 rounded-full" style={{ backgroundColor: cat.color }} />
<div className="w-2 h-2 rounded-full" style={{ backgroundColor: reg.color }} />
</div>
) : (
<div className="w-4 h-4 rounded-full bg-white/[0.03]" />
@@ -151,11 +185,19 @@ export default function RegulatoryLandscapeSlide({ lang }: RegulatoryLandscapeSl
)
})}
<div className="text-center">
<span className="text-xs font-bold text-white/80">{industry.regCount}</span>
<span className="text-xs font-bold text-white/80">{industry.totalDocs}</span>
</div>
</div>
)
})}
))}
{/* Footer note */}
<div className="pt-2 mt-1 border-t border-white/5">
<p className="text-xs text-white/50 text-center">
{de
? '244 Dokumente gelten horizontal für alle Branchen (DSGVO, BDSG, AI Act, NIS2, CRA, BetrVG, HGB, ...). Sektorspezifische Regulierungen kommen hinzu.'
: '244 documents apply horizontally to all industries (GDPR, BDSG, AI Act, NIS2, CRA, ...). Sector-specific regulations are added on top.'}
</p>
</div>
</div>
</GlassCard>
</FadeInView>
@@ -4,6 +4,7 @@ import { useState } from 'react'
import { Language } from '@/lib/types'
import { t } from '@/lib/i18n'
import GradientText from '../ui/GradientText'
import BrandName from '../ui/BrandName'
import FadeInView from '../ui/FadeInView'
import GlassCard from '../ui/GlassCard'
import { Shield, Scale, Wifi, Lock, Calendar, AlertTriangle, CheckCircle2, Clock } from 'lucide-react'
@@ -47,9 +48,9 @@ export default function RegulatorySlide({ lang }: RegulatorySlideProps) {
keyRequirements: de
? [
'Verzeichnis von Verarbeitungstaetigkeiten (VVT)',
'Datenschutz-Folgenabschaetzung (DSFA)',
'Technische und organisatorische Massnahmen (TOM)',
'Betroffenenrechte (Auskunft, Loeschung, Portabilitaet)',
'Datenschutz-Folgenabschätzung (DSFA)',
'Technische und organisatorische Maßnahmen (TOM)',
'Betroffenenrechte (Auskunft, Löschung, Portabilität)',
'Auftragsverarbeitungsvertraege (AVV)',
'Datenschutzbeauftragter (ab 20 MA)',
'Meldepflicht bei Datenpannen (72h)',
@@ -67,9 +68,9 @@ export default function RegulatorySlide({ lang }: RegulatorySlideProps) {
howWeHelp: de
? [
'Automatische VVT-Erstellung aus Unternehmensdaten',
'KI-gestuetzte DSFA-Durchfuehrung',
'KI-gestützte DSFA-Durchführung',
'TOM-Generator mit Branchenvorlagen',
'Self-Service-Portal fuer Betroffenenanfragen',
'Self-Service-Portal für Betroffenenanfragen',
'Automatische Dokumentation und Audit-Trail',
]
: [
@@ -85,17 +86,17 @@ export default function RegulatorySlide({ lang }: RegulatorySlideProps) {
status: de ? 'Schrittweise ab Aug 2025' : 'Phased from Aug 2025',
statusColor: 'text-amber-400',
statusIcon: Clock,
deadline: de ? 'Aug 2025: Verbote · Aug 2026: Hochrisiko · Aug 2027: Vollstaendig' : 'Aug 2025: Prohibitions · Aug 2026: High-Risk · Aug 2027: Full',
deadline: de ? 'Aug 2025: Verbote · Aug 2026: Hochrisiko · Aug 2027: Vollständig' : 'Aug 2025: Prohibitions · Aug 2026: High-Risk · Aug 2027: Full',
affectedCompanies: de ? 'Anbieter und Betreiber von KI-Systemen in der EU' : 'Providers and deployers of AI systems in the EU',
keyRequirements: de
? [
'Risikoklassifizierung aller KI-Systeme (Art. 6)',
'Konformitaetsbewertung fuer Hochrisiko-KI (Art. 43)',
'Konformitätsbewertung für Hochrisiko-KI (Art. 43)',
'Technische Dokumentation und Transparenz (Art. 11-13)',
'Menschliche Aufsicht (Art. 14)',
'Registrierung in EU-Datenbank (Art. 49)',
'GPAI-Modell-Pflichten (Art. 51-56)',
'Grundrechte-Folgenabschaetzung (Art. 27)',
'Grundrechte-Folgenabschätzung (Art. 27)',
]
: [
'Risk classification of all AI systems (Art. 6)',
@@ -110,10 +111,10 @@ export default function RegulatorySlide({ lang }: RegulatorySlideProps) {
howWeHelp: de
? [
'Automatische Risikoklassifizierung von KI-Systemen',
'Konformitaets-Checklisten mit KI-Unterstuetzung',
'Konformitäts-Checklisten mit KI-Unterstützung',
'Technische Dokumentation per Template-Engine',
'Audit-Vorbereitung fuer Hochrisiko-Systeme',
'Monitoring von Rechtsaenderungen',
'Audit-Vorbereitung für Hochrisiko-Systeme',
'Monitoring von Rechtsänderungen',
]
: [
'Automatic AI system risk classification',
@@ -128,17 +129,17 @@ export default function RegulatorySlide({ lang }: RegulatorySlideProps) {
status: de ? 'In Kraft seit Dez 2024' : 'In effect since Dec 2024',
statusColor: 'text-amber-400',
statusIcon: Clock,
deadline: de ? 'Sep 2026: Meldepflichten · Dez 2027: Vollstaendig anzuwenden' : 'Sep 2026: Reporting · Dec 2027: Fully applicable',
deadline: de ? 'Sep 2026: Meldepflichten · Dez 2027: Vollständig anzuwenden' : 'Sep 2026: Reporting · Dec 2027: Fully applicable',
affectedCompanies: de ? 'Alle Hersteller von Produkten mit digitalen Elementen (Hardware & Software)' : 'All manufacturers of products with digital elements (hardware & software)',
keyRequirements: de
? [
'Security by Design fuer alle Produkte mit Software',
'Schwachstellen-Management ueber gesamten Produktlebenszyklus',
'Software Bill of Materials (SBOM) fuer jedes Produkt',
'Kostenlose Sicherheitsupdates fuer Kunden',
'Security by Design für alle Produkte mit Software',
'Schwachstellen-Management über gesamten Produktlebenszyklus',
'Software Bill of Materials (SBOM) für jedes Produkt',
'Kostenlose Sicherheitsupdates für Kunden',
'Meldepflicht bei aktiv ausgenutzten Schwachstellen (24h)',
'Konformitaetsbewertung durch Drittstelle (fuer kritische Produkte)',
'CE-Kennzeichnung fuer Cybersecurity-Compliance',
'Konformitätsbewertung durch Drittstelle (für kritische Produkte)',
'CE-Kennzeichnung für Cybersecurity-Compliance',
]
: [
'Security by design for all products with software',
@@ -156,7 +157,7 @@ export default function RegulatorySlide({ lang }: RegulatorySlideProps) {
'Kontinuierliches Schwachstellen-Scanning (Trivy, Grype)',
'Security-Fixes durch 1000B Cloud-LLM implementiert',
'CRA-konforme Dokumentation und Audit-Trail',
'Risikoanalysen fuer Embedded-Software und Firmware',
'Risikoanalysen für Embedded-Software und Firmware',
]
: [
'Automatic SBOM generation from code repositories',
@@ -175,13 +176,13 @@ export default function RegulatorySlide({ lang }: RegulatorySlideProps) {
affectedCompanies: de ? '30.000+ Unternehmen in DE (Energie, Transport, Gesundheit, Digital, KRITIS)' : '30,000+ companies in DE (Energy, Transport, Healthcare, Digital, Critical Infrastructure)',
keyRequirements: de
? [
'Risikomanagement-Massnahmen (Art. 21)',
'Risikomanagement-Maßnahmen (Art. 21)',
'Incident-Meldepflichten: 24h Fruehwarnung, 72h Bericht (Art. 23)',
'Business Continuity und Krisenmanagement',
'Supply-Chain-Security (Lieferkettenrisiken)',
'Geschaeftsleiterhaftung (persoenliche Haftung)',
'Registrierung beim BSI',
'Regelmaessige Audits und Nachweise',
'Regelmäßige Audits und Nachweise',
]
: [
'Risk management measures (Art. 21)',
@@ -196,7 +197,7 @@ export default function RegulatorySlide({ lang }: RegulatorySlideProps) {
howWeHelp: de
? [
'Cybersecurity-Policy-Generator nach BSI-Grundschutz',
'Incident-Response-Plaene mit KI-Unterstuetzung',
'Incident-Response-Pläne mit KI-Unterstützung',
'Supply-Chain-Risikoanalyse',
'Automatische Audit-Dokumentation',
'NIS2-Readiness-Assessment',
@@ -237,7 +238,7 @@ export default function RegulatorySlide({ lang }: RegulatorySlideProps) {
className={`flex items-center gap-2 px-4 py-2 rounded-xl text-sm transition-all
${activeTab === tab.id
? 'bg-indigo-500/20 text-indigo-300 border border-indigo-500/30'
: 'bg-white/[0.04] text-white/40 border border-transparent hover:text-white/60 hover:bg-white/[0.06]'
: 'bg-white/[0.04] text-white/40 border border-transparent hover:text-white/60 hover:bg-white/[0.06] animate-[pulse_3s_ease-in-out_infinite]'
}`}
>
<Icon className="w-4 h-4" />
@@ -272,8 +273,8 @@ export default function RegulatorySlide({ lang }: RegulatorySlideProps) {
</GlassCard>
<GlassCard hover={false} className="p-4">
<p className="text-xs font-semibold text-emerald-400 uppercase tracking-wider mb-2">
{de ? 'Wie ComplAI hilft' : 'How ComplAI Helps'}
<p className="text-xs font-semibold text-white uppercase tracking-wider mb-2">
{de ? <>Wie <BrandName /> hilft</> : <>How <BrandName /> Helps</>}
</p>
<ul className="space-y-1.5">
{reg.howWeHelp.map((item, idx) => (
@@ -289,7 +290,7 @@ export default function RegulatorySlide({ lang }: RegulatorySlideProps) {
{/* Right: Requirements */}
<div className="md:col-span-7">
<GlassCard hover={false} className="p-4 h-full">
<p className="text-xs font-semibold text-white/40 uppercase tracking-wider mb-3">
<p className="text-xs font-semibold text-white uppercase tracking-wider mb-3">
{de ? 'Kernanforderungen' : 'Key Requirements'}
</p>
<div className="space-y-2">
+176
View File
@@ -0,0 +1,176 @@
'use client'
import { Language } from '@/lib/types'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
import GlassCard from '../ui/GlassCard'
import { AlertTriangle, Shield, Clock, Cpu, Globe, Users, TrendingUp } from 'lucide-react'
interface RiskSlideProps {
lang: Language
}
export default function RiskSlide({ lang }: RiskSlideProps) {
const de = lang === 'de'
const risks = [
{
icon: Cpu,
color: 'text-red-400',
border: 'border-red-500/20',
risk_de: 'KI-Commoditisierung',
risk_en: 'AI Commoditization',
desc_de: 'LLMs senken Eintrittsbarrieren — Control-Generierung, DSFA-Erstellung und Policy-Templates werden zur Commodity.',
desc_en: 'LLMs lower entry barriers — control generation, DPIA creation and policy templates become commodities.',
mitigation_de: 'Wir konkurrieren nicht auf Layer 1 (KI-Wissen), sondern auf Layer 2-6: Integration, Auditierbarkeit, Workflows, EU-Hosting. KI ist unser Multiplikator, nicht unser Produkt.',
mitigation_en: 'We don\'t compete on Layer 1 (AI knowledge) but on Layers 2-6: integration, auditability, workflows, EU hosting. AI is our multiplier, not our product.',
timeline_de: 'Mittel (3-5 J.)',
timeline_en: 'Medium (3-5 yrs)',
severity: 'high',
},
{
icon: Globe,
color: 'text-amber-400',
border: 'border-amber-500/20',
risk_de: 'US-Plattform-Expansion',
risk_en: 'US Platform Expansion',
desc_de: 'Microsoft Purview, Vanta oder Drata expandieren nach Europa mit lokalisiertem Compliance-Angebot.',
desc_en: 'Microsoft Purview, Vanta or Drata expand to Europe with localized compliance offerings.',
mitigation_de: 'Struktureller Vorteil: 100% EU-Infrastruktur, kein US-SaaS im Stack, Betriebsrat-Fähigkeit. US-Anbieter können den CLOUD Act nicht umgehen — das ist für deutsche Mittelständler ein Ausschlusskriterium.',
mitigation_en: 'Structural advantage: 100% EU infrastructure, no US SaaS in the stack, works council compliance. US providers cannot circumvent the CLOUD Act — that is a deal-breaker for German mid-market.',
timeline_de: 'Mittel (2-4 J.)',
timeline_en: 'Medium (2-4 yrs)',
severity: 'medium',
},
{
icon: Users,
color: 'text-amber-400',
border: 'border-amber-500/20',
risk_de: 'Team-Risiko / Key-Person',
risk_en: 'Team Risk / Key Person',
desc_de: 'Abhängigkeit von zwei Gründern in der Frühphase. Wissensverlust bei Ausfall.',
desc_en: 'Dependency on two founders in the early phase. Knowledge loss in case of absence.',
mitigation_de: 'Dokumentation aller Prozesse in MkDocs. KI-gestützte Codebasis (500k+ Zeilen mit Tests). ESOP-Pool für Schlüsselmitarbeiter ab Hire 1. Redundanz durch frühe Einstellung eines Rechtsanwalts/Datenschutz.',
mitigation_en: 'Documentation of all processes in MkDocs. AI-assisted codebase (500k+ lines with tests). ESOP pool for key employees from hire 1. Redundancy through early hiring of a lawyer/data protection expert.',
timeline_de: 'Hoch (Jahr 1-2)',
timeline_en: 'High (Year 1-2)',
severity: 'medium',
},
{
icon: TrendingUp,
color: 'text-blue-400',
border: 'border-blue-500/20',
risk_de: 'Langsame Kundenakquise',
risk_en: 'Slow Customer Acquisition',
desc_de: 'B2B-Verkaufszyklen im Mittelstand dauern 3-9 Monate. Compliance-Budgets werden jährlich geplant.',
desc_en: 'B2B sales cycles in the mid-market take 3-9 months. Compliance budgets are planned annually.',
mitigation_de: 'Beratungsumsätze ab Gründung (5-30k/Mon) überbrücken die Anlaufphase. Channel-Strategie über Bechtle/CANCOM skaliert schneller als Direktvertrieb. Land-and-Expand: Einstieg mit einem Modul, Upsell auf Full Compliance.',
mitigation_en: 'Consulting revenue from founding (5-30k/month) bridges the ramp-up. Channel strategy via Bechtle/CANCOM scales faster than direct sales. Land-and-expand: entry with one module, upsell to full compliance.',
timeline_de: 'Hoch (Jahr 1-3)',
timeline_en: 'High (Year 1-3)',
severity: 'medium',
},
{
icon: Shield,
color: 'text-emerald-400',
border: 'border-emerald-500/20',
risk_de: 'Regulatorische Änderungen',
risk_en: 'Regulatory Changes',
desc_de: 'Neue EU-Gesetze oder Änderungen bestehender Regularien erfordern schnelle Anpassung der Plattform.',
desc_en: 'New EU laws or changes to existing regulations require rapid platform adaptation.',
mitigation_de: 'Jede regulatorische Änderung vergrößert unseren Markt. RAG-Pipeline kann neue Regularien innerhalb von Tagen indexieren. Über 380 Regularien bereits in der Wissensbasis — Vorsprung von Jahren.',
mitigation_en: 'Every regulatory change enlarges our market. RAG pipeline can index new regulations within days. Over 380 regulations already in the knowledge base — years of head start.',
timeline_de: 'Laufend',
timeline_en: 'Ongoing',
severity: 'low',
},
{
icon: Clock,
color: 'text-emerald-400',
border: 'border-emerald-500/20',
risk_de: 'Liquiditätsrisiko',
risk_en: 'Liquidity Risk',
desc_de: 'Mit 200k Wandeldarlehen ist die Runway begrenzt. Ende 2027 nahe Null.',
desc_en: 'With a 200k convertible loan, the runway is limited. Near zero by end of 2027.',
mitigation_de: 'Organisches Wachstum durch Beratungsumsätze. Break-Even in 2029. Option auf Pre-Seed BW (L-Bank) verdoppelt die Gesamtfinanzierung auf 400k. Lean-Team mit 9 Personen bis 2030.',
mitigation_en: 'Organic growth through consulting revenue. Break-even in 2029. Option for Pre-Seed BW (L-Bank) doubles total funding to 400k. Lean team with 9 people until 2030.',
timeline_de: 'Hoch (Jahr 1-2)',
timeline_en: 'High (Year 1-2)',
severity: 'low',
},
]
return (
<div className="max-w-6xl mx-auto">
<FadeInView className="text-center mb-5">
<h2 className="text-3xl md:text-4xl font-bold mb-2">
<GradientText>{de ? 'Risiken & Mitigation' : 'Risks & Mitigation'}</GradientText>
</h2>
<p className="text-sm text-white/50 max-w-3xl mx-auto">
{de
? 'Transparente Darstellung der wesentlichen Risiken und unserer konkreten Gegenmaßnahmen.'
: 'Transparent presentation of key risks and our concrete countermeasures.'}
</p>
</FadeInView>
<div className="space-y-3">
{risks.map((r, idx) => {
const Icon = r.icon
const severityColor = r.severity === 'high' ? 'bg-red-500/20 text-red-300 border-red-500/30'
: r.severity === 'medium' ? 'bg-amber-500/20 text-amber-300 border-amber-500/30'
: 'bg-emerald-500/20 text-emerald-300 border-emerald-500/30'
return (
<FadeInView key={idx} delay={0.05 + idx * 0.05}>
<GlassCard hover={false} className={`p-4 border-l-2 ${r.border}`}>
<div className="grid md:grid-cols-12 gap-3">
{/* Risk */}
<div className="md:col-span-5">
<div className="flex items-center gap-2 mb-1.5">
<Icon className={`w-4 h-4 ${r.color}`} />
<h3 className={`text-sm font-bold ${r.color}`}>{de ? r.risk_de : r.risk_en}</h3>
<span className={`text-[9px] px-1.5 py-0.5 rounded-full border ${severityColor}`}>
{de ? r.timeline_de : r.timeline_en}
</span>
</div>
<p className="text-xs text-white/40 leading-relaxed">
{de ? r.desc_de : r.desc_en}
</p>
</div>
{/* Arrow */}
<div className="hidden md:flex items-center justify-center">
<div className="w-6 h-[1px] bg-white/10" />
<AlertTriangle className="w-3 h-3 text-white/20 mx-1" />
<div className="w-6 h-[1px] bg-white/10" />
</div>
{/* Mitigation */}
<div className="md:col-span-6">
<p className="text-[10px] text-emerald-400/60 uppercase tracking-wider mb-1 font-semibold">
{de ? 'Mitigation' : 'Mitigation'}
</p>
<p className="text-xs text-white/60 leading-relaxed">
{de ? r.mitigation_de : r.mitigation_en}
</p>
</div>
</div>
</GlassCard>
</FadeInView>
)
})}
</div>
{/* Key Insight */}
<FadeInView delay={0.4} className="mt-4">
<div className="bg-gradient-to-r from-indigo-500/10 to-purple-500/10 border border-indigo-500/20 rounded-xl px-5 py-3 text-center">
<p className="text-sm text-white/70 italic">
{de
? '„Wir konkurrieren nicht mit KI. Wir konkurrieren mit Teams, die KI besser einsetzen als wir. Deshalb bauen wir nicht das beste LLM — sondern die vertrauenswürdigste Compliance-Infrastruktur."'
: '"We don\'t compete with AI. We compete with teams that use AI better than we do. That is why we don\'t build the best LLM — but the most trustworthy compliance infrastructure."'}
</p>
</div>
</FadeInView>
</div>
)
}
+46 -20
View File
@@ -1,6 +1,6 @@
'use client'
import { useState, useEffect, useCallback } from 'react'
import { useState, useEffect, useCallback, useRef } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import Image from 'next/image'
import { Language } from '@/lib/types'
@@ -43,6 +43,26 @@ export default function SDKDemoSlide({ lang }: SDKDemoSlideProps) {
const [fullscreen, setFullscreen] = useState(false)
const [autoPlay, setAutoPlay] = useState(true)
// Track which images have actually loaded so we never cross-fade to a blank
// frame. While the target image is still fetching, `shown` stays on the
// previous loaded one — this eliminates the flash of empty canvas the user
// hit on the first pass through the carousel.
const loadedRef = useRef<Set<number>>(new Set())
const [shown, setShown] = useState(0)
const handleLoaded = useCallback((idx: number) => {
loadedRef.current.add(idx)
// If the user is currently waiting on this image, reveal it immediately.
// Otherwise the preceding loaded image keeps showing — no blank flash.
if (idx === current) setShown(idx)
}, [current])
useEffect(() => {
if (loadedRef.current.has(current)) {
setShown(current)
}
}, [current])
const next = useCallback(() => {
setCurrent(i => (i + 1) % SCREENSHOTS.length)
}, [])
@@ -71,8 +91,8 @@ export default function SDKDemoSlide({ lang }: SDKDemoSlideProps) {
</h2>
<p className="text-base text-white/50 max-w-2xl mx-auto">
{de
? 'Echte Screenshots aus dem Compliance SDK — Kundenprojekt: Müller Maschinenbau GmbH'
: 'Real screenshots from the Compliance SDK — Customer project: Müller Maschinenbau GmbH'}
? 'Echte Screenshots aus dem Compliance SDK — Beispielkunde: Muster Maschinenbau GmbH'
: 'Real screenshots from the Compliance SDK — Example customer: Muster Maschinenbau GmbH'}
</p>
</FadeInView>
@@ -90,7 +110,7 @@ export default function SDKDemoSlide({ lang }: SDKDemoSlideProps) {
</div>
<div className="flex-1 ml-3">
<div className="bg-white/[0.06] rounded-md px-3 py-1 text-xs text-white/30 font-mono max-w-md">
admin.breakpilot.ai/sdk/{shot.file.replace(/^\d+-/, '').replace('.png', '')}
admin-dev.breakpilot.ai/sdk/{shot.file.replace(/^\d+-/, '').replace('.png', '')}
</div>
</div>
<button
@@ -101,25 +121,31 @@ export default function SDKDemoSlide({ lang }: SDKDemoSlideProps) {
</button>
</div>
{/* Screenshot */}
<AnimatePresence mode="wait">
<motion.div
key={current}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.3 }}
{/* Screenshot stack all images mount at once so we can cross-fade
between them by toggling opacity. AnimatePresence mode="wait"
unmounts before the next mounts, which forces a cold fetch and
produces a blank frame; the stack avoids both. */}
<div className="relative aspect-[1920/1080] bg-black/40">
{SCREENSHOTS.map((s, idx) => (
<div
key={s.file}
className="absolute inset-0 transition-opacity duration-300 ease-out"
style={{ opacity: idx === shown ? 1 : 0 }}
aria-hidden={idx !== shown}
>
<Image
src={`/screenshots/${shot.file}`}
alt={de ? shot.de : shot.en}
width={1920}
height={1080}
className="w-full h-auto"
priority={current < 3}
src={`/screenshots/${s.file}`}
alt={de ? s.de : s.en}
fill
sizes="(max-width: 1024px) 100vw, 1024px"
className="object-cover"
priority={idx < 3}
loading={idx < 3 ? undefined : 'eager'}
onLoadingComplete={() => handleLoaded(idx)}
/>
</motion.div>
</AnimatePresence>
</div>
))}
</div>
</div>
{/* Navigation arrows */}
+28 -28
View File
@@ -19,60 +19,60 @@ export default function SavingsSlide({ lang }: SavingsSlideProps) {
color: 'text-indigo-400',
bg: 'border-indigo-500/20',
name: de ? 'KMU (25 MA)' : 'SME (25 emp.)',
desc: de ? '2-3 Apps, 1-2 Produkte mit SW' : '2-3 apps, 1-2 products with SW',
desc: de ? '2-3 Anwendungen, 1-2 Produkte mit Software' : '2-3 applications, 1-2 products with software',
bp_price: de ? '15.000 EUR/Jahr' : 'EUR 15,000/yr',
savings: [
{ label: de ? 'Pentests (2-3 Apps × 6.000€)' : 'Pentests (2-3 apps × €6,000)', without: '18.000', with: '5.000', save: '13.000' },
{ label: de ? 'Pentests (2-3 Anwendungen × 6.000€)' : 'Pentests (2-3 applications × €6,000)', without: '18.000', with: '5.000', save: '13.000' },
{ label: de ? 'CE-SW-Risikobeurteilung (1-2 Produkte)' : 'CE SW risk assessment (1-2 products)', without: '12.000', with: '3.000', save: '9.000' },
{ label: de ? 'Ext. Datenschutzbeauftragter' : 'External DPO', without: '6.000', with: '0', save: '6.000' },
{ label: de ? 'Compliance-Dokumentation (VVT, TOMs)' : 'Compliance docs (RoPA, TOMs)', without: '8.000', with: '0', save: '8.000' },
{ label: de ? 'Entwickler-Produktivität (Shift-Left)' : 'Developer productivity (shift-left)', without: '26.000', with: '10.000', save: '16.000' },
{ label: de ? 'Ext. Datenschutzbeauftragter' : 'External DPO', without: '6.000', with: '3.000', save: '3.000' },
{ label: de ? 'Compliance-Dokumentation (VVT, TOMs)' : 'Compliance docs (RoPA, TOMs)', without: '8.000', with: '2.000', save: '6.000' },
{ label: de ? 'Produktivere Compliance-Arbeitszeit (~50%)' : 'More productive compliance time (~50%)', without: '30.000', with: '15.000', save: '15.000' },
{ label: de ? 'Audit-Vorbereitung' : 'Audit preparation', without: '12.000', with: '3.000', save: '9.000' },
],
totalWithout: '97.750',
totalWith: '44.530',
totalSave: '53.220',
roi: '3,5x',
totalWithout: '86.000',
totalWith: '31.000',
totalSave: '55.000',
roi: '3,7x',
},
{
icon: Factory,
color: 'text-emerald-400',
bg: 'border-emerald-500/20',
name: de ? 'Mittelstand (100 MA)' : 'Mid-size (100 emp.)',
desc: de ? '5-8 Apps, 3-5 Produkte, MES/ERP' : '5-8 apps, 3-5 products, MES/ERP',
desc: de ? '5-8 Anwendungen, 3-5 Produkte, MES/ERP' : '5-8 applications, 3-5 products, MES/ERP',
bp_price: de ? '30.000 EUR/Jahr' : 'EUR 30,000/yr',
savings: [
{ label: de ? 'Pentests (5-8 Apps × 8.000€)' : 'Pentests (5-8 apps × €8,000)', without: '56.000', with: '15.000', save: '41.000' },
{ label: de ? 'Pentests (5-8 Anwendungen × 8.000€)' : 'Pentests (5-8 applications × €8,000)', without: '56.000', with: '15.000', save: '41.000' },
{ label: de ? 'CE-SW-Risiko (3-5 Produkte × 15.000€)' : 'CE SW risk (3-5 products × €15,000)', without: '60.000', with: '10.000', save: '50.000' },
{ label: de ? 'Compliance-Team (0,5 FTE Audit Manager)' : 'Compliance team (0.5 FTE audit manager)', without: '35.000', with: '10.000', save: '25.000' },
{ label: de ? 'Produktivere Compliance-Arbeitszeit (~50%)' : 'More productive compliance time (~50%)', without: '70.000', with: '35.000', save: '35.000' },
{ label: de ? 'TISAX / ISO 27001 Zertifizierung' : 'TISAX / ISO 27001 certification', without: '25.000', with: '8.000', save: '17.000' },
{ label: de ? 'Entwickler-Produktivität (5 Devs)' : 'Developer productivity (5 devs)', without: '130.000', with: '50.000', save: '80.000' },
{ label: de ? 'Compliance-Team (0,5 FTE Audit Manager)' : 'Compliance team (0.5 FTE audit manager)', without: '35.000', with: '15.000', save: '20.000' },
{ label: de ? 'CRA/NIS2 Compliance-Aufwand' : 'CRA/NIS2 compliance effort', without: '45.000', with: '15.000', save: '30.000' },
],
totalWithout: '419.500',
totalWith: '193.880',
totalSave: '225.620',
roi: '7,5x',
totalWithout: '291.000',
totalWith: '98.000',
totalSave: '193.000',
roi: '6,4x',
},
{
icon: Building,
color: 'text-amber-400',
bg: 'border-amber-500/20',
name: de ? 'Konzern (500+ MA)' : 'Enterprise (500+ emp.)',
desc: de ? '15-25 Apps, 10-20 Produkte, SCADA/ICS' : '15-25 apps, 10-20 products, SCADA/ICS',
desc: de ? '15-25 Anwendungen, 10-20 Produkte, SCADA/ICS' : '15-25 applications, 10-20 products, SCADA/ICS',
bp_price: de ? '50.000 EUR/Jahr' : 'EUR 50,000/yr',
savings: [
{ label: de ? 'Pentests (15-25 Apps × 10.000€)' : 'Pentests (15-25 apps × €10,000)', without: '200.000', with: '50.000', save: '150.000' },
{ label: de ? 'Pentests (15-25 Anwendungen × 10.000€)' : 'Pentests (15-25 applications × €10,000)', without: '200.000', with: '50.000', save: '150.000' },
{ label: de ? 'CE-SW-Risiko (10-20 Produkte × 18.000€)' : 'CE SW risk (10-20 products × €18,000)', without: '270.000', with: '50.000', save: '220.000' },
{ label: de ? 'Compliance-Abteilung (2-3 FTE)' : 'Compliance department (2-3 FTE)', without: '250.000', with: '100.000', save: '150.000' },
{ label: de ? 'Externe Berater (TÜV, DEKRA, Anwälte)' : 'External consultants (TÜV, DEKRA, lawyers)', without: '120.000', with: '30.000', save: '90.000' },
{ label: de ? 'Entwickler-Produktivität (20+ Devs)' : 'Developer productivity (20+ devs)', without: '540.000', with: '200.000', save: '340.000' },
{ label: de ? 'Produktivere Compliance-Arbeitszeit (~50%)' : 'More productive compliance time (~50%)', without: '200.000', with: '100.000', save: '100.000' },
{ label: de ? 'Externe Berater (TÜV, DEKRA, Anwälte)' : 'External consultants (TÜV, DEKRA, lawyers)', without: '120.000', with: '40.000', save: '80.000' },
{ label: de ? 'Compliance-Abteilung (2-3 FTE)' : 'Compliance department (2-3 FTE)', without: '250.000', with: '120.000', save: '130.000' },
{ label: de ? 'Incident Response / Strafvermeidung' : 'Incident response / penalty avoidance', without: '150.000', with: '50.000', save: '100.000' },
],
totalWithout: '2.113.500',
totalWith: '1.074.080',
totalSave: '1.039.420',
roi: '20,8x',
totalWithout: '1.190.000',
totalWith: '410.000',
totalSave: '780.000',
roi: '15,6x',
},
]
@@ -100,7 +100,7 @@ export default function SavingsSlide({ lang }: SavingsSlideProps) {
<Icon className={`w-6 h-6 ${co.color}`} />
<div>
<h3 className={`text-sm font-bold ${co.color}`}>{co.name}</h3>
<p className="text-[10px] text-white/40">{co.desc}</p>
<p className="text-xs text-white/40">{co.desc}</p>
</div>
</div>
<div className="text-right">
@@ -110,7 +110,7 @@ export default function SavingsSlide({ lang }: SavingsSlideProps) {
</div>
{/* Savings table */}
<div className="grid grid-cols-[1fr_80px_80px_80px] gap-x-2 text-[10px] text-white/30 uppercase tracking-wider mb-1.5 border-b border-white/10 pb-1">
<div className="grid grid-cols-[1fr_80px_80px_80px] gap-x-2 text-xs text-white/30 uppercase tracking-wider mb-1.5 border-b border-white/10 pb-1">
<span>{de ? 'Kostenposition' : 'Cost Item'}</span>
<span className="text-right">{de ? 'Ohne' : 'Without'}</span>
<span className="text-right">{de ? 'Mit' : 'With'}</span>
@@ -18,6 +18,7 @@ const colors = ['from-blue-500 to-cyan-500', 'from-indigo-500 to-purple-500', 'f
export default function SolutionSlide({ lang }: SolutionSlideProps) {
const i = t(lang)
const de = lang === 'de'
return (
<div>
@@ -52,6 +53,27 @@ export default function SolutionSlide({ lang }: SolutionSlideProps) {
)
})}
</div>
{/* Compliance Optimizer MOAT */}
<FadeInView delay={0.8}>
<div className="bg-gradient-to-r from-amber-500/10 to-indigo-500/10 border border-amber-500/20 rounded-xl px-6 py-5 mt-8 max-w-4xl mx-auto">
<div className="flex items-center gap-4">
<div className="w-16 h-16 rounded-2xl bg-gradient-to-br from-amber-500 to-orange-600 flex items-center justify-center shrink-0 shadow-lg">
<Bot className="w-8 h-8 text-white" />
</div>
<div>
<h3 className="text-xl font-bold text-amber-300 mb-1">
{de ? 'Compliance Optimizer' : 'Compliance Optimizer'}
</h3>
<p className="text-sm text-white/50 leading-relaxed">
{de
? 'Nicht nur „erlaubt/verboten" — unsere Plattform zeigt die maximal zulässige Ausgestaltung jedes KI-Use-Cases. Statt Einschränkung: optimale Ausnutzung des regulatorischen Raums — deterministisch, automatisiert und auditierbar.'
: 'Not just "allowed/forbidden" — our platform shows the maximum permissible configuration of every AI use case. Instead of restriction: optimal utilization of the regulatory space — deterministic, automated and auditable.'}
</p>
</div>
</div>
</div>
</FadeInView>
</div>
)
}
+101 -50
View File
@@ -9,11 +9,98 @@ import { Shield, ScanLine, FileText, Rocket, Users, Building2, Globe, TrendingUp
interface StrategySlideProps {
lang: Language
isWandeldarlehen?: boolean
}
export default function StrategySlide({ lang }: StrategySlideProps) {
export default function StrategySlide({ lang, isWandeldarlehen }: StrategySlideProps) {
const de = lang === 'de'
const phasesLean = [
{
icon: Rocket, color: 'text-indigo-400', bg: 'bg-indigo-500/10 border-indigo-500/20',
title: de ? 'Phase 1: Gründung' : 'Phase 1: Foundation',
period: 'Aug 2026 Jul 2027',
team: de ? '3 Personen' : '3 people',
arr: de ? '~60k EUR' : '~EUR 60k',
items: de
? ['Gründer + Rechtsanwalt/Datenschutz', 'Erste 5-6 Pilotkunden (SaaS)', 'Beratungsumsätze 5-10k/Mon', 'Prototyp → Produktivbetrieb']
: ['Founders + lawyer/data protection', 'First 5-6 pilot customers (SaaS)', 'Consulting revenue 5-10k/month', 'Prototype → production'],
},
{
icon: Building2, color: 'text-purple-400', bg: 'bg-purple-500/10 border-purple-500/20',
title: de ? 'Phase 2: Produkt' : 'Phase 2: Product',
period: 'Aug 2027 Jul 2028',
team: de ? '4-5 Personen' : '4-5 people',
arr: de ? '~200k EUR' : '~EUR 200k',
items: de
? ['Erster Entwickler (Full-Stack)', 'Security Engineer für Scanner-Kern', '8-10 Bestandskunden', 'Beratung steigt auf 20k/Mon']
: ['First developer (full-stack)', 'Security engineer for scanner core', '8-10 active customers', 'Consulting grows to 20k/month'],
},
{
icon: Users, color: 'text-emerald-400', bg: 'bg-emerald-500/10 border-emerald-500/20',
title: de ? 'Phase 3: Vertrieb' : 'Phase 3: Sales',
period: 'Aug 2028 Jul 2029',
team: de ? '5-7 Personen' : '5-7 people',
arr: de ? '~500k1M EUR' : '~EUR 500k1M',
items: de
? ['Erste dedizierte Vertriebsperson', 'Backend-Entwickler für Skalierung', '15-30 Bestandskunden', 'Break-Even in Sicht (2029)']
: ['First dedicated salesperson', 'Backend developer for scaling', '15-30 active customers', 'Break-even in sight (2029)'],
},
{
icon: Globe, color: 'text-amber-400', bg: 'bg-amber-500/10 border-amber-500/20',
title: de ? 'Phase 4: Wachstum' : 'Phase 4: Growth',
period: 'Aug 2029 Dez 2030',
team: de ? '7-10 Personen' : '7-10 people',
arr: de ? '~2-3M EUR' : '~EUR 2-3M',
items: de
? ['Customer Success + Marketing', 'DevOps für Infrastruktur', '50-200+ Bestandskunden', 'Profitabel — organisches Wachstum']
: ['Customer success + marketing', 'DevOps for infrastructure', '50-200+ active customers', 'Profitable — organic growth'],
},
]
const phases1M = [
{
icon: Rocket, color: 'text-indigo-400', bg: 'bg-indigo-500/10 border-indigo-500/20',
title: de ? 'Phase 1: Foundation' : 'Phase 1: Foundation',
period: 'Aug 2026 Jun 2027',
team: de ? '5 Mitarbeiter' : '5 employees',
arr: '75150k EUR',
items: de
? ['Security Engineer + CE-Risikoingenieur als erste Hires', '5 Pilotkunden im Maschinenbau', 'Gründer verkaufen selbst', 'Product-Market Fit beweisen']
: ['Security Engineer + CE Risk Engineer as first hires', '5 pilot customers in manufacturing', 'Founders sell themselves', 'Prove product-market fit'],
},
{
icon: Building2, color: 'text-purple-400', bg: 'bg-purple-500/10 border-purple-500/20',
title: de ? 'Phase 2: Traction' : 'Phase 2: Traction',
period: 'Jul 2027 Jun 2028',
team: de ? '10 Mitarbeiter' : '10 employees',
arr: '0,51,2M EUR',
items: de
? ['Channel Manager für Bechtle/CANCOM', 'DevSecOps + KI-Ingenieur', 'Lösungsberater für Partner-Demos', 'Wiederholbarer Vertriebsprozess']
: ['Channel Manager for Bechtle/CANCOM', 'DevSecOps + AI engineer', 'Solutions engineer for partner demos', 'Repeatable sales process'],
},
{
icon: Users, color: 'text-emerald-400', bg: 'bg-emerald-500/10 border-emerald-500/20',
title: de ? 'Phase 3: Scale' : 'Phase 3: Scale',
period: 'Jul 2028 Jun 2029',
team: de ? '17→25 Mitarbeiter' : '17→25 employees',
arr: '24M EUR',
items: de
? ['Erster Direktvertrieb neben Channel', 'Compliance-Jurist für Glaubwürdigkeit', 'Security-Analyst / Pentester', 'VP Sales übernimmt vom CEO']
: ['First direct sales alongside channel', 'Compliance lawyer for credibility', 'Security analyst / pentester', 'VP Sales takes over from CEO'],
},
{
icon: Globe, color: 'text-amber-400', bg: 'bg-amber-500/10 border-amber-500/20',
title: de ? 'Phase 4: Leadership' : 'Phase 4: Leadership',
period: 'Jul 2029 Dez 2030',
team: de ? '25→35 Mitarbeiter' : '25→35 employees',
arr: '410M EUR',
items: de
? ['EU-Expansion (AT, CH, Benelux)', 'Enterprise-Vertrieb', 'Developer Relations (Snyk-Modell)', 'Break-Even oder Series A']
: ['EU expansion (AT, CH, Benelux)', 'Enterprise sales', 'Developer Relations (Snyk model)', 'Break-even or Series A'],
},
]
return (
<div className="max-w-6xl mx-auto">
<FadeInView className="text-center mb-6">
@@ -77,48 +164,7 @@ export default function StrategySlide({ lang }: StrategySlideProps) {
{de ? 'Firmenaufbau in 4 Phasen' : 'Company Building in 4 Phases'}
</h3>
<div className="grid md:grid-cols-4 gap-3">
{[
{
icon: Rocket, color: 'text-indigo-400', bg: 'bg-indigo-500/10 border-indigo-500/20',
title: de ? 'Phase 1: Foundation' : 'Phase 1: Foundation',
period: 'Aug 2026 Jun 2027',
team: de ? '5 Mitarbeiter' : '5 employees',
arr: '75150k EUR',
items: de
? ['Security Engineer + CE-Risikoingenieur als erste Hires', '5 Pilotkunden im Maschinenbau', 'Gründer verkaufen selbst', 'Product-Market Fit beweisen']
: ['Security Engineer + CE Risk Engineer as first hires', '5 pilot customers in manufacturing', 'Founders sell themselves', 'Prove product-market fit'],
},
{
icon: Building2, color: 'text-purple-400', bg: 'bg-purple-500/10 border-purple-500/20',
title: de ? 'Phase 2: Traction' : 'Phase 2: Traction',
period: 'Jul 2027 Jun 2028',
team: de ? '10 Mitarbeiter' : '10 employees',
arr: '0,51,2M EUR',
items: de
? ['Channel Manager für Bechtle/CANCOM', 'DevSecOps + KI-Ingenieur', 'Lösungsberater für Partner-Demos', 'Wiederholbarer Vertriebsprozess']
: ['Channel Manager for Bechtle/CANCOM', 'DevSecOps + AI engineer', 'Solutions engineer for partner demos', 'Repeatable sales process'],
},
{
icon: Users, color: 'text-emerald-400', bg: 'bg-emerald-500/10 border-emerald-500/20',
title: de ? 'Phase 3: Scale' : 'Phase 3: Scale',
period: 'Jul 2028 Jun 2029',
team: de ? '17→25 Mitarbeiter' : '17→25 employees',
arr: '24M EUR',
items: de
? ['Erster Direktvertrieb neben Channel', 'Compliance-Jurist für Glaubwürdigkeit', 'Security-Analyst / Pentester', 'VP Sales übernimmt vom CEO']
: ['First direct sales alongside channel', 'Compliance lawyer for credibility', 'Security analyst / pentester', 'VP Sales takes over from CEO'],
},
{
icon: Globe, color: 'text-amber-400', bg: 'bg-amber-500/10 border-amber-500/20',
title: de ? 'Phase 4: Leadership' : 'Phase 4: Leadership',
period: 'Jul 2029 Dez 2030',
team: de ? '25→35 Mitarbeiter' : '25→35 employees',
arr: '410M EUR',
items: de
? ['EU-Expansion (AT, CH, Benelux)', 'Enterprise-Vertrieb', 'Developer Relations (Snyk-Modell)', 'Break-Even oder Series A']
: ['EU expansion (AT, CH, Benelux)', 'Enterprise sales', 'Developer Relations (Snyk model)', 'Break-even or Series A'],
},
].map((phase, idx) => {
{(isWandeldarlehen ? phasesLean : phases1M).map((phase, idx) => {
const Icon = phase.icon
return (
<GlassCard key={idx} delay={0.25 + idx * 0.05} hover={false} className={`p-3 ${phase.bg} border`}>
@@ -126,14 +172,14 @@ export default function StrategySlide({ lang }: StrategySlideProps) {
<Icon className={`w-4 h-4 ${phase.color}`} />
<h4 className={`text-xs font-bold ${phase.color}`}>{phase.title}</h4>
</div>
<p className="text-[10px] text-white/30 mb-1">{phase.period}</p>
<div className="flex justify-between text-[10px] mb-2">
<p className="text-xs text-white/30 mb-1">{phase.period}</p>
<div className="flex justify-between text-xs mb-2">
<span className="text-white/50">{phase.team}</span>
<span className={`font-mono font-bold ${phase.color}`}>{phase.arr}</span>
</div>
<div className="space-y-1">
{phase.items.map((item, i) => (
<p key={i} className="text-[10px] text-white/60 pl-3 relative">
<p key={i} className="text-xs text-white/60 pl-3 relative">
<span className={`absolute left-0 top-1 w-1.5 h-1.5 rounded-full ${phase.color.replace('text-', 'bg-')}/60`} />
{item}
</p>
@@ -154,9 +200,9 @@ export default function StrategySlide({ lang }: StrategySlideProps) {
<GlassCard delay={0.5} hover={false} className="p-4 border-t-2 border-t-blue-500">
<div className="flex items-center justify-between mb-2">
<h4 className="text-sm font-bold text-blue-400">CANCOM Cloud Marketplace</h4>
<span className="text-[9px] bg-blue-500/20 text-blue-300 px-2 py-0.5 rounded-full">{de ? 'Schneller Einstieg' : 'Fast Entry'}</span>
<span className="text-[11px] bg-blue-500/20 text-blue-300 px-2 py-0.5 rounded-full">{de ? 'Schneller Einstieg' : 'Fast Entry'}</span>
</div>
<p className="text-[10px] text-white/40 mb-2">{de ? 'TecDAX · ~5.800 MA · 120+ SaaS-Produkte gelistet' : 'TecDAX · ~5,800 emp. · 120+ SaaS products listed'}</p>
<p className="text-xs text-white/40 mb-2">{de ? 'TecDAX · ~5.800 MA · 120+ SaaS-Produkte gelistet' : 'TecDAX · ~5,800 emp. · 120+ SaaS products listed'}</p>
<div className="space-y-1.5">
{(de ? [
'Formales ISV-Partnerprogramm — strukturiertes Onboarding',
@@ -180,9 +226,9 @@ export default function StrategySlide({ lang }: StrategySlideProps) {
<GlassCard delay={0.55} hover={false} className="p-4 border-t-2 border-t-emerald-500">
<div className="flex items-center justify-between mb-2">
<h4 className="text-sm font-bold text-emerald-400">Bechtle Systemhäuser</h4>
<span className="text-[9px] bg-emerald-500/20 text-emerald-300 px-2 py-0.5 rounded-full">{de ? 'Größte Reichweite' : 'Largest Reach'}</span>
<span className="text-[11px] bg-emerald-500/20 text-emerald-300 px-2 py-0.5 rounded-full">{de ? 'Größte Reichweite' : 'Largest Reach'}</span>
</div>
<p className="text-[10px] text-white/40 mb-2">{de ? '15.000 MA · 85+ Standorte · 6,3 Mrd. EUR · 70.000 Kunden' : '15,000 emp. · 85+ locations · EUR 6.3B · 70,000 customers'}</p>
<p className="text-xs text-white/40 mb-2">{de ? '15.000 MA · 85+ Standorte · 6,3 Mrd. EUR · 70.000 Kunden' : '15,000 emp. · 85+ locations · EUR 6.3B · 70,000 customers'}</p>
<div className="space-y-1.5">
{(de ? [
'Regionaler Einstieg: Lokales Systemhaus wo wir Kunden haben',
@@ -203,6 +249,11 @@ export default function StrategySlide({ lang }: StrategySlideProps) {
</div>
</GlassCard>
</div>
<p className="text-xs text-white/30 text-center mt-2 italic">
{de
? '* CANCOM und Bechtle sind geplante Distributionspartner. Eine Kontaktaufnahme ist noch nicht erfolgt.'
: '* CANCOM and Bechtle are planned distribution partners. No contact has been made yet.'}
</p>
</FadeInView>
{/* Channel-First Quote */}
+58 -38
View File
@@ -3,7 +3,7 @@
import { motion } from 'framer-motion'
import { Language, PitchTeamMember } from '@/lib/types'
import { t } from '@/lib/i18n'
import { User, Linkedin } from 'lucide-react'
import { User, Linkedin, Github } from 'lucide-react'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
import Image from 'next/image'
@@ -13,89 +13,109 @@ interface TeamSlideProps {
team: PitchTeamMember[]
}
function equityDisplay(pct: number | string | null | undefined): string {
const n = Number(pct)
if (!Number.isFinite(n)) return '—'
return Number.isInteger(n) ? `${n}%` : `${n.toFixed(1)}%`
}
function detectProfileLink(url: string | null | undefined): { icon: typeof Linkedin | typeof Github; label: string } | null {
if (!url) return null
if (url.includes('github.com')) return { icon: Github, label: 'GitHub' }
if (url.includes('linkedin.com')) return { icon: Linkedin, label: 'LinkedIn' }
return { icon: Linkedin, label: 'Profile' }
}
export default function TeamSlide({ lang, team }: TeamSlideProps) {
const i = t(lang)
return (
<div>
<FadeInView className="text-center mb-12">
<FadeInView className="text-center mb-8">
<h2 className="text-4xl md:text-5xl font-bold mb-3">
<GradientText>{i.team.title}</GradientText>
</h2>
<p className="text-lg text-white/50 max-w-2xl mx-auto">{i.team.subtitle}</p>
</FadeInView>
<div className="grid md:grid-cols-2 gap-8 max-w-4xl mx-auto">
{team.map((member, idx) => (
<div className="grid md:grid-cols-2 gap-6 max-w-5xl mx-auto items-stretch">
{team.map((member, idx) => {
const link = detectProfileLink(member.linkedin_url)
const LinkIcon = link?.icon
return (
<motion.div
key={member.id}
initial={{ opacity: 0, x: idx === 0 ? -40 : 40 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.3 + idx * 0.2, duration: 0.6 }}
className="bg-white/[0.08] backdrop-blur-xl border border-white/10 rounded-3xl p-8"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 + idx * 0.15, duration: 0.5 }}
className="bg-white/[0.04] backdrop-blur-xl border border-white/[0.08] rounded-2xl p-6 flex flex-col hover:border-indigo-500/20 transition-colors"
>
<div className="flex items-start gap-5">
{/* Avatar — Foto oder Fallback */}
{/* Header: avatar + name + role */}
<div className="flex items-center gap-4 mb-5">
{member.photo_url ? (
<div className="w-20 h-20 rounded-2xl overflow-hidden shrink-0 shadow-lg">
<div className="w-16 h-16 rounded-2xl overflow-hidden shrink-0 shadow-lg">
<Image
src={member.photo_url}
alt={member.name}
width={80}
height={80}
width={64}
height={64}
className="w-full h-full object-cover"
/>
</div>
) : (
<div className="w-20 h-20 rounded-2xl bg-gradient-to-br from-indigo-500 to-purple-600
flex items-center justify-center shrink-0 shadow-lg">
<User className="w-10 h-10 text-white" />
<div className="w-16 h-16 rounded-2xl bg-gradient-to-br from-indigo-500 to-purple-600 flex items-center justify-center shrink-0 shadow-lg shadow-indigo-500/20">
<User className="w-8 h-8 text-white" />
</div>
)}
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<h3 className="text-xl font-bold text-white">{member.name}</h3>
{member.linkedin_url && (
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2 mb-0.5 flex-wrap">
<h3 className="text-xl font-bold text-white truncate">{member.name}</h3>
{link && LinkIcon && (
<a
href={member.linkedin_url}
href={member.linkedin_url!}
target="_blank"
rel="noopener noreferrer"
className="p-1 text-white/30 hover:text-[#0A66C2] transition-colors"
title="LinkedIn"
className="text-white/30 hover:text-indigo-300 transition-colors"
title={link.label}
>
<Linkedin className="w-4 h-4" />
<LinkIcon className="w-4 h-4" />
</a>
)}
</div>
<p className="text-indigo-400 text-sm font-medium mb-3">
<p className="text-indigo-400 text-sm font-medium">
{lang === 'de' ? member.role_de : member.role_en}
</p>
<p className="text-sm text-white/50 leading-relaxed mb-4">
</div>
{/* Equity pill in top-right */}
<div className="text-right shrink-0">
<div className="text-[10px] uppercase tracking-wider text-white/30">{i.team.equity}</div>
<div className="text-base font-bold text-white tabular-nums">{equityDisplay(member.equity_pct)}</div>
</div>
</div>
{/* Bio */}
<p className="text-sm text-white/60 leading-relaxed mb-5 flex-1">
{lang === 'de' ? member.bio_de : member.bio_en}
</p>
{/* Equity */}
<div className="flex items-center gap-2 mb-3">
<span className="text-xs text-white/40">{i.team.equity}:</span>
<span className="text-sm font-bold text-white">{member.equity_pct}%</span>
</div>
{/* Expertise Tags */}
<div className="flex flex-wrap gap-1.5">
{/* Expertise tags */}
{(member.expertise || []).length > 0 && (
<div className="flex flex-wrap gap-1.5 pt-4 border-t border-white/[0.06]">
{(member.expertise || []).map((skill, sidx) => (
<span
key={sidx}
className="text-xs px-2.5 py-1 rounded-full bg-indigo-500/10 text-indigo-300 border border-indigo-500/20"
className="text-[11px] px-2.5 py-1 rounded-full bg-indigo-500/10 text-indigo-300 border border-indigo-500/20"
>
{skill}
</span>
))}
</div>
</div>
</div>
)}
</motion.div>
))}
)
})}
</div>
</div>
)
+138 -59
View File
@@ -3,20 +3,19 @@
import { motion } from 'framer-motion'
import { Language, PitchFunding } from '@/lib/types'
import { t } from '@/lib/i18n'
import ProjectionFooter from '../ui/ProjectionFooter'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
import AnimatedCounter from '../ui/AnimatedCounter'
import GlassCard from '../ui/GlassCard'
import { Target, Calendar, FileText } from 'lucide-react'
import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip } from 'recharts'
import { Landmark, Banknote, ArrowRightLeft, TrendingUp, ShieldCheck, Target, Calendar, FileText } from 'lucide-react'
interface TheAskSlideProps {
lang: Language
funding: PitchFunding
isWandeldarlehen?: boolean
}
const COLORS = ['#6366f1', '#a78bfa', '#60a5fa', '#34d399', '#fbbf24']
function formatFundingAmount(amount: number): { target: number; suffix: string } {
if (amount >= 1_000_000) {
return { target: Math.round(amount / 100_000) / 10, suffix: ' Mio.' }
@@ -38,17 +37,13 @@ function formatTargetDate(dateStr: string, lang: Language): string {
}
}
export default function TheAskSlide({ lang, funding }: TheAskSlideProps) {
export default function TheAskSlide({ lang, funding, isWandeldarlehen }: TheAskSlideProps) {
const i = t(lang)
const useOfFunds = funding?.use_of_funds || []
const amount = funding?.amount_eur || 0
const de = lang === 'de'
const isWD = isWandeldarlehen || (funding?.instrument || '').toLowerCase() === 'wandeldarlehen'
const amount = Number(funding?.amount_eur) || 0
const { target, suffix } = formatFundingAmount(amount)
const pieData = useOfFunds.map((item) => ({
name: lang === 'de' ? item.label_de : item.label_en,
value: item.percentage,
}))
return (
<div>
<FadeInView className="text-center mb-10">
@@ -70,6 +65,48 @@ export default function TheAskSlide({ lang, funding }: TheAskSlideProps) {
<span className="text-3xl md:text-4xl text-white/50 ml-2">EUR</span>
</p>
</motion.div>
{isWandeldarlehen && (
<div className="space-y-3 mt-4">
{/* Row 1: 200k Scenario */}
<div className="flex items-center justify-center gap-3">
<div className="text-center px-4 py-2 bg-indigo-500/10 border border-indigo-500/20 rounded-xl">
<p className="text-xs text-white/40">{de ? 'Ihr Investment' : 'Your Investment'}</p>
<p className="text-lg font-bold text-indigo-400">40k EUR</p>
<p className="text-[10px] text-white/30">{de ? 'ab 20% — auch mehr möglich' : 'from 20% — more possible'}</p>
</div>
<span className="text-2xl text-white/30 font-light">+</span>
<div className="text-center px-4 py-2 bg-emerald-500/10 border border-emerald-500/20 rounded-xl">
<p className="text-xs text-white/40">L-Bank Pre-Seed</p>
<p className="text-lg font-bold text-emerald-400">160k EUR</p>
</div>
<span className="text-2xl text-white/30 font-light">=</span>
<div className="text-center px-4 py-2 bg-white/5 border border-white/10 rounded-xl">
<p className="text-xs text-white/40">{de ? 'Gesamtfinanzierung' : 'Total Funding'}</p>
<p className="text-lg font-bold text-white">200k EUR</p>
</div>
</div>
{/* Row 2: 400k Scenario (optional) */}
<div className="flex items-center justify-center gap-3 opacity-60">
<div className="text-center px-3 py-1.5 bg-indigo-500/5 border border-indigo-500/10 rounded-xl">
<p className="text-[10px] text-white/30">{de ? 'Ihr Investment' : 'Your Investment'}</p>
<p className="text-sm font-bold text-indigo-400/70">80k EUR</p>
</div>
<span className="text-lg text-white/20 font-light">+</span>
<div className="text-center px-3 py-1.5 bg-emerald-500/5 border border-emerald-500/10 rounded-xl">
<p className="text-[10px] text-white/30">L-Bank Pre-Seed</p>
<p className="text-sm font-bold text-emerald-400/70">320k EUR</p>
</div>
<span className="text-lg text-white/20 font-light">=</span>
<div className="text-center px-3 py-1.5 bg-white/5 border border-white/10 rounded-xl">
<p className="text-[10px] text-white/30">{de ? 'Gesamtfinanzierung' : 'Total Funding'}</p>
<p className="text-sm font-bold text-white/70">400k EUR</p>
</div>
</div>
<p className="text-[10px] text-white/25 text-center">
{de ? 'Optional: doppelte Tranche bei höherem Investor-Anteil' : 'Optional: double tranche with higher investor share'}
</p>
</div>
)}
</FadeInView>
{/* Details — dynamisch aus funding-Objekt */}
@@ -93,60 +130,102 @@ export default function TheAskSlide({ lang, funding }: TheAskSlideProps) {
</GlassCard>
</div>
{/* Use of Funds */}
<FadeInView delay={0.8}>
{/* Wandeldarlehen Explanation — only shown for Wandeldarlehen versions */}
{isWandeldarlehen && (
<>
{/* How it works */}
<FadeInView delay={0.5} className="mb-6">
<GlassCard hover={false} className="p-6">
<h3 className="text-lg font-semibold text-white mb-4 text-center">{i.theAsk.useOfFunds}</h3>
<div className="flex flex-col md:flex-row items-center gap-8">
{/* Pie Chart */}
<div className="w-48 h-48">
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={pieData}
cx="50%"
cy="50%"
innerRadius={50}
outerRadius={80}
dataKey="value"
stroke="none"
>
{pieData.map((_, idx) => (
<Cell key={idx} fill={COLORS[idx % COLORS.length]} />
))}
</Pie>
<Tooltip
contentStyle={{
background: 'rgba(10, 10, 26, 0.9)',
border: '1px solid rgba(255,255,255,0.1)',
borderRadius: 8,
color: '#fff',
fontSize: 13,
}}
formatter={(value: number) => `${value}%`}
/>
</PieChart>
</ResponsiveContainer>
<h3 className="text-lg font-semibold text-white mb-4 text-center">
{de ? 'So funktioniert das Wandeldarlehen' : 'How the Convertible Loan Works'}
</h3>
<div className="grid md:grid-cols-3 gap-4">
<div className="text-center p-4 bg-white/5 rounded-xl">
<Banknote className="w-6 h-6 text-indigo-400 mx-auto mb-2" />
<p className="text-sm font-bold text-white mb-1">
{de ? '1. Investition' : '1. Investment'}
</p>
<p className="text-xs text-white/50 leading-relaxed">
{de
? 'Investor stellt Darlehen bereit — keine sofortige Bewertung, keine sofortige Verwässerung.'
: 'Investor provides a loan — no immediate valuation, no immediate dilution.'}
</p>
</div>
{/* Legend */}
<div className="flex-1 space-y-3">
{useOfFunds.map((item, idx) => (
<div key={idx} className="flex items-center gap-3">
<div className="w-3 h-3 rounded-full" style={{ backgroundColor: COLORS[idx] }} />
<span className="flex-1 text-sm text-white/70">
{lang === 'de' ? item.label_de : item.label_en}
</span>
<span className="text-sm font-bold text-white">{item.percentage}%</span>
<span className="text-xs text-white/30">
{((amount * item.percentage) / 100).toLocaleString('de-DE')} EUR
</span>
<div className="text-center p-4 bg-white/5 rounded-xl">
<ArrowRightLeft className="w-6 h-6 text-purple-400 mx-auto mb-2" />
<p className="text-sm font-bold text-white mb-1">
{de ? '2. Conversion' : '2. Conversion'}
</p>
<p className="text-xs text-white/50 leading-relaxed">
{de
? 'Bei der nächsten Finanzierungsrunde wandelt das Darlehen in Anteile — mit Discount für den Frühphasen-Investor.'
: 'At the next funding round, the loan converts to equity — with a discount for the early-stage investor.'}
</p>
</div>
))}
<div className="text-center p-4 bg-white/5 rounded-xl">
<TrendingUp className="w-6 h-6 text-emerald-400 mx-auto mb-2" />
<p className="text-sm font-bold text-white mb-1">
{de ? '3. Investor-Vorteil' : '3. Investor Advantage'}
</p>
<p className="text-xs text-white/50 leading-relaxed">
{de
? 'Durch den Discount erhält der Investor mehr Anteile pro Euro als spätere Investoren — Prämie für frühes Vertrauen.'
: 'The discount gives the investor more shares per euro than later investors — a premium for early trust.'}
</p>
</div>
</div>
</GlassCard>
</FadeInView>
{/* Pre-Seed BW / L-Bank */}
<FadeInView delay={0.6} className="mb-6">
<div className="bg-gradient-to-r from-emerald-500/10 to-blue-500/10 border border-emerald-500/20 rounded-xl px-5 py-3">
<div className="flex items-start gap-3">
<ShieldCheck className="w-5 h-5 text-emerald-400 mt-0.5 shrink-0" />
<div>
<p className="text-sm font-bold text-white mb-1">
{de ? 'Pre-Seed BW — Staatliche Co-Finanzierung (L-Bank)' : 'Pre-Seed BW — Government Co-Financing (L-Bank)'}
</p>
<p className="text-xs text-white/50 leading-relaxed">
{de
? 'Das Investment wird über das Pre-Seed-Programm von Start-up BW / L-Bank staatlich co-finanziert. Die L-Bank stellt eine Zuwendung mit Wandlungsvorbehalt bereit — das reduziert das Risiko für private Investoren und signalisiert staatliches Vertrauen in das Geschäftsmodell.'
: 'The investment is co-financed through the Pre-Seed BW / L-Bank government program. L-Bank provides a grant with conversion option — reducing risk for private investors and signaling government confidence in the business model.'}
</p>
</div>
</div>
</div>
</FadeInView>
</>
)}
{/* INVEST Program Hint */}
<FadeInView delay={0.6} className="mt-4">
<div className="bg-gradient-to-r from-indigo-500/10 to-emerald-500/10 border border-indigo-500/20 rounded-xl px-5 py-3">
<div className="flex items-start gap-3">
<Landmark className="w-5 h-5 text-indigo-400 mt-0.5 shrink-0" />
<div>
<p className="text-sm font-bold text-white mb-1">
{de
? 'BAFA INVEST — Zuschuss für Wagniskapital (Kombinierbarkeit mit L-Bank Wandeldarlehen muss geprüft werden)'
: 'BAFA INVEST — Venture Capital Grant (Compatibility with L-Bank convertible loan must be verified)'}
</p>
<p className="text-xs text-white/50 leading-relaxed">
{de
? 'Investoren erhalten über das BAFA INVEST-Programm bis zu 15% steuerfreien Erwerbszuschuss auf ihr Investment (max. 50.000 EUR pro Einzelinvestment) sowie zusätzlich 25% Exit-Zuschuss auf Veräußerungsgewinne. Effektive Förderung: bis zu 40% (Entry + Exit kombiniert). Voraussetzung: natürliche Person, Mindesthaltedauer 3 Jahre.'
: 'Investors receive up to 15% tax-free acquisition grant on their investment through the BAFA INVEST program (max. EUR 50,000 per single investment) plus an additional 25% exit grant on capital gains. Effective support: up to 40% (entry + exit combined). Requirements: natural person, 3-year minimum holding period.'}
</p>
<p className="text-[11px] text-white/30 mt-1 italic">
{de
? '* Programm verlängert bis 31.12.2026. Aktuelle Konditionen auf bafa.de prüfen.'
: '* Program extended until 31.12.2026. Verify current terms at bafa.de.'}
</p>
</div>
</div>
</div>
</FadeInView>
<ProjectionFooter lang={lang} />
</div>
)
}
+847
View File
@@ -0,0 +1,847 @@
'use client'
import { useState, useEffect, useRef, useMemo } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { Language } from '@/lib/types'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
import { X } from 'lucide-react'
interface USPSlideProps { lang: Language }
const MONO: React.CSSProperties = {
fontFamily: '"JetBrains Mono","SF Mono",ui-monospace,monospace',
fontVariantNumeric: 'tabular-nums',
}
const CSS_KF = `
@keyframes uspFlowR { 0%{stroke-dashoffset:0} 100%{stroke-dashoffset:-14px} }
@keyframes uspSpin { from{transform:rotate(0deg)} to{transform:rotate(360deg)} }
@keyframes uspPulse {
0%,100% { box-shadow: 0 0 38px rgba(167,139,250,.55), 0 0 80px rgba(167,139,250,.2), inset 0 3px 0 rgba(255,255,255,.35), inset 0 -6px 12px rgba(0,0,0,.35); }
50% { box-shadow: 0 0 58px rgba(167,139,250,.85), 0 0 110px rgba(167,139,250,.35), inset 0 3px 0 rgba(255,255,255,.4), inset 0 -6px 12px rgba(0,0,0,.35); }
}
@keyframes uspPulseLight {
0%,100% { box-shadow: 0 0 28px rgba(167,139,250,.4), 0 0 56px rgba(167,139,250,.15), inset 0 3px 0 rgba(255,255,255,.5), inset 0 -6px 12px rgba(0,0,0,.2); }
50% { box-shadow: 0 0 44px rgba(167,139,250,.65), 0 0 80px rgba(167,139,250,.25), inset 0 3px 0 rgba(255,255,255,.55), inset 0 -6px 12px rgba(0,0,0,.2); }
}
@keyframes uspHeading {
0%,100% { text-shadow: 0 0 22px rgba(167,139,250,.3); }
50% { text-shadow: 0 0 36px rgba(167,139,250,.55); }
}
`
// ── Light mode hook ───────────────────────────────────────────────────────────
function useIsLight() {
const [isLight, setIsLight] = useState(false)
useEffect(() => {
const check = () => setIsLight(document.documentElement.classList.contains('theme-light'))
check()
const obs = new MutationObserver(check)
obs.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] })
return () => obs.disconnect()
}, [])
return isLight
}
// ── Ticker ────────────────────────────────────────────────────────────────────
function useTicker(fn: () => void, min = 180, max = 420, skip = 0.1) {
const ref = useRef(fn)
ref.current = fn
useEffect(() => {
let t: ReturnType<typeof setTimeout>
const loop = () => {
if (Math.random() > skip) ref.current()
t = setTimeout(loop, min + Math.random() * (max - min))
}
loop()
return () => clearTimeout(t)
}, [min, max, skip])
}
function TickerShell({ tint, isLight, children }: { tint: string; isLight: boolean; children: React.ReactNode }) {
return (
<div style={{
...MONO, marginTop: 10, padding: '5px 9px',
background: isLight ? '#f1f5f9' : 'rgba(0,0,0,.38)',
border: `1px solid ${tint}55`,
borderRadius: 6, fontSize: 10.5,
color: isLight ? '#475569' : 'rgba(236,233,247,.88)',
display: 'flex', alignItems: 'center', gap: 7,
whiteSpace: 'nowrap', overflow: 'hidden', height: 22,
}}>{children}</div>
)
}
function TickTrace({ tint, isLight }: { tint: string; isLight: boolean }) {
const [n, setN] = useState(12748)
useTicker(() => setN(v => v + 1 + Math.floor(Math.random() * 3)), 250, 500)
return (
<TickerShell tint={tint} isLight={isLight}>
<span style={{ color: isLight ? '#16a34a' : '#4ade80' }}></span>
<span style={{ color: tint, opacity: .85 }}>trace</span>
<span style={{ color: isLight ? '#1a1a2e' : '#f5f3fc', fontWeight: 600 }}>{n.toLocaleString()}</span>
<span style={{ color: isLight ? '#94a3b8' : 'rgba(236,233,247,.45)' }}>evidence-chain</span>
</TickerShell>
)
}
function TickEngine({ tint, isLight }: { tint: string; isLight: boolean }) {
const [v, setV] = useState(428)
const [rate, setRate] = useState(99.4)
useTicker(() => {
setV(x => x + 1 + Math.floor(Math.random() * 4))
setRate(r => Math.max(97, Math.min(99.9, r + (Math.random() - 0.5) * 0.3)))
}, 220, 420)
return (
<TickerShell tint={tint} isLight={isLight}>
<span style={{ color: isLight ? '#16a34a' : '#4ade80' }}></span>
<span style={{ color: tint, opacity: .85 }}>validate</span>
<span style={{ color: isLight ? '#1a1a2e' : '#f5f3fc', fontWeight: 600 }}>{v.toLocaleString()}</span>
<span style={{ color: isLight ? '#16a34a' : '#4ade80' }}>{rate.toFixed(1)}%</span>
</TickerShell>
)
}
function TickOptimizer({ tint, isLight }: { tint: string; isLight: boolean }) {
const ops = ['ROI: 2.418 € / dev', 'gap → policy §4.2', 'dedup 128 tickets', 'sweet-spot: 22 KLOC', 'tradeoff: speed↔risk']
const [i, setI] = useState(0)
useTicker(() => setI(x => (x + 1) % ops.length), 900, 1600, 0.05)
return (
<TickerShell tint={tint} isLight={isLight}>
<span style={{ color: '#fbbf24' }}></span>
<span style={{ color: tint, opacity: .85 }}>optimize</span>
<span style={{ color: isLight ? '#1a1a2e' : '#f5f3fc', overflow: 'hidden', textOverflow: 'ellipsis', flex: 1 }}>{ops[i]}</span>
</TickerShell>
)
}
function TickStack({ tint, isLight }: { tint: string; isLight: boolean }) {
const regs = ['DSGVO', 'NIS-2', 'DORA', 'EU AI Act', 'ISO 27001', 'BSI C5']
const [i, setI] = useState(0)
const [c, setC] = useState(1208)
useTicker(() => { setI(x => (x + 1) % regs.length); setC(v => v + Math.floor(Math.random() * 3)) }, 800, 1400, 0.05)
return (
<TickerShell tint={tint} isLight={isLight}>
<span style={{ color: isLight ? '#16a34a' : '#4ade80' }}></span>
<span style={{ color: tint, opacity: .85 }}>check</span>
<span style={{ color: isLight ? '#1a1a2e' : '#f5f3fc', fontWeight: 600 }}>{regs[i]}</span>
<span style={{ color: isLight ? '#94a3b8' : 'rgba(236,233,247,.4)' }}>·</span>
<span style={{ color: isLight ? '#1a1a2e' : '#f5f3fc' }}>{c.toLocaleString()}</span>
</TickerShell>
)
}
// ── Data ──────────────────────────────────────────────────────────────────────
interface DetailItem {
tint: string
icon: string
kicker: string
title: string
body: string
bullets?: string[]
stat?: { k: string; v: string }
}
function getDetails(de: boolean): Record<string, DetailItem> {
return {
rfq: {
tint: '#a78bfa', icon: '⇄',
kicker: de ? 'Säule · Compliance' : 'Pillar · Compliance',
title: de ? 'RFQ-Prüfung' : 'RFQ Verification',
body: de
? 'Kunden-Anforderungsdokumente werden automatisch gegen den aktuellen Source-Code geprüft. Abweichungen werden erkannt, Änderungen vorgeschlagen und auf Wunsch direkt im Code umgesetzt — ohne manuelles Nacharbeiten.'
: 'Customer requirement documents are automatically verified against current source code. Deviations are detected, changes proposed and implemented directly in code on request — no manual rework needed.',
bullets: de
? ['Klauseln automatisch gegen SBOM, SAST-Findings und Policy-Docs abgeglichen', 'Lücken mit konkreten Implementierungsvorschlägen markiert', 'RFQ-Antworten in Stunden statt Wochen']
: ['Auto-match clauses against SBOM, SAST findings and policy docs', 'Flag gaps with concrete implementation proposals', 'Win-ready RFQ replies in hours, not weeks'],
stat: { k: de ? 'Ø Antwortzeit' : 'avg response time', v: de ? '4,2h (war 12 Tage)' : '4.2h (was 12 days)' },
},
process: {
tint: '#c084fc', icon: '⟲',
kicker: de ? 'Säule · Compliance' : 'Pillar · Compliance',
title: de ? 'Prozess-Compliance' : 'Process Compliance',
body: de
? 'Vom Audit-Finding über das Ticket bis zur Code-Änderung läuft der gesamte Prozess automatisiert durch. Rollen, Fristen und Eskalation werden End-to-End verwaltet. Nachweise werden automatisch generiert und archiviert.'
: 'From audit finding to ticket to code change, the entire process runs automatically. Roles, deadlines and escalation are managed end-to-end. Evidence is automatically generated and archived.',
bullets: de
? ['Finding → Ticket → PR → Nachweis in einem Thread', 'SLA-Tracking pro Control mit Auto-Eskalation', 'Unveränderliches Audit-Log, pro Änderung signiert']
: ['Finding → ticket → PR → evidence in one thread', 'SLA tracking per control with auto-escalation', 'Immutable audit log signed per change'],
stat: { k: de ? 'automatisierte Prozessschritte' : 'process steps automated', v: '87%' },
},
bidir: {
tint: '#fbbf24', icon: '⟷',
kicker: de ? 'Säule · Code' : 'Pillar · Code',
title: de ? 'Bidirektional' : 'Bidirectional Sync',
body: de
? 'Compliance-Anforderungen fliessen direkt in den Code. Umgekehrt aktualisieren Code-Änderungen automatisch die Compliance-Dokumentation. Beide Seiten sind immer synchron — kein Informationsverlust zwischen Audit und Entwicklung.'
: 'Compliance requirements flow directly into code. Conversely, code changes automatically update compliance documentation. Both sides always stay in sync — no information loss between audit and development.',
bullets: de
? ['Policy ↔ Code-Mapping via semantischem Diff', 'Git-nativ: jede Änderung als PR', 'Zero Drift zwischen Audit-Artefakten und Realität']
: ['Policy ↔ code mapping via semantic diff', 'Git-native: every change shipped as a PR', 'Zero drift between audit artefacts and reality'],
stat: { k: de ? 'Drift-Vorfälle' : 'drift incidents', v: de ? '0 seit März 2024' : '0 since Mar-2024' },
},
cont: {
tint: '#f59e0b', icon: '◎',
kicker: de ? 'Säule · Code' : 'Pillar · Code',
title: de ? 'Kontinuierlich' : 'Continuous, Not Yearly',
body: de
? 'Klassische Compliance prüft einmal im Jahr und hofft auf das Beste. Unsere Plattform prüft bei jeder Code-Änderung. Findings werden sofort zu Tickets mit konkreten Implementierungsvorschlägen im Issue-Tracker der Wahl.'
: 'Traditional compliance checks once a year and hopes for the best. Our platform checks on every code change. Findings immediately become tickets with concrete implementation proposals in the issue tracker of choice.',
bullets: de
? ['CI-integrierte Validierung bei jedem Push', 'Fix-Vorschläge generiert, nicht nur gemeldet', 'Compliance-Frische: Minuten statt Monate']
: ['CI-integrated validation on each push', 'Fix suggestions generated, not just reported', 'Compliance freshness: minutes, not months'],
stat: { k: de ? 'Validierungen / Tag' : 'validations / day', v: '~2.400 / repo' },
},
trace: {
tint: '#a78bfa', icon: '⇄',
kicker: de ? 'Under the Hood' : 'Under the Hood',
title: de ? 'End-to-End Rückverfolgbarkeit' : 'End-to-End Traceability',
body: de
? 'Regulatorische Anforderungen (Gesetz → Obligation → Control) deterministisch mit realem Systemzustand und Code verknüpft — inklusive revisionssicherem Evidence-Layer.'
: 'Regulatory requirements (law → obligation → control) deterministically linked to real system state and code — including audit-proof evidence layer.',
bullets: de
? ['Versionierter Evidence-Chain, unveränderlich gespeichert', 'Ein Klick von Klausel bis Codezeile', 'Signierte Attestierungen pro Build']
: ['Versioned evidence chain stored immutably', 'One-click drill from clause to line of code', 'Signed attestations per build'],
},
engine: {
tint: '#c084fc', icon: '◉',
kicker: de ? 'Under the Hood' : 'Under the Hood',
title: de ? 'Continuous Compliance Engine' : 'Continuous Compliance Engine',
body: de
? 'Statt punktueller Audits: Validierung bei jeder Änderung (Code, Infrastruktur, Prozesse) mit auditierbaren Nachweisen in Echtzeit.'
: 'Instead of point-in-time audits: validation on every change (code, infrastructure, processes) with auditable evidence in real time.',
bullets: de
? ['Rule-Packs pro Framework (NIS-2, DORA, …)', 'Verarbeitet Code, IaC und Prozess-Events', 'Findings automatisch ans richtige Team geroutet']
: ['Rule packs per framework (NIS-2, DORA, …)', 'Handles code, infra-as-code, and process events', 'Findings routed to the right team automatically'],
},
opt: {
tint: '#fbbf24', icon: '✦',
kicker: de ? 'Under the Hood' : 'Under the Hood',
title: de ? 'Compliance Optimizer' : 'Compliance Optimizer',
body: de
? 'Nicht nur „erlaubt/verboten", sondern die maximal zulässige Ausgestaltung jedes KI-Use-Cases. Deterministische Constraint-Optimierung zeigt den Sweet Spot zwischen Regulierung und Innovation — ersetzt 20200k EUR Anwaltskosten.'
: 'Not just "allowed/forbidden" but the maximum permissible configuration of every AI use case. Deterministic constraint optimization shows the sweet spot between regulation and innovation — replaces EUR 20200k in legal fees.',
bullets: de
? ['ROI-Ranking jedes offenen Findings', 'Abwägung zwischen Liefergeschwindigkeit und Restrisiko', 'Low-Hanging-Wins zuerst']
: ['ROI-ranks every open finding', 'Balances speed of delivery with residual risk', 'Highlights low-hanging wins first'],
},
stack: {
tint: '#f59e0b', icon: '◎',
kicker: de ? 'Under the Hood' : 'Under the Hood',
title: de ? 'EU-Trust & Governance Stack' : 'EU Trust & Governance Stack',
body: de
? 'Souveräne, DSGVO-/AI-Act-konforme Architektur (EU-Hosting, Isolation, Betriebsrat-Fähigkeit) — Marktzugang, den US-Lösungen strukturell nicht erreichen.'
: 'Sovereign, GDPR/AI Act compliant architecture (EU hosting, isolation, works council capability) — market access that US solutions structurally cannot achieve.',
bullets: de
? ['DSGVO · NIS-2 · DORA · EU AI Act · ISO 27001 · BSI C5', 'EU-souveränes Hosting und Key-Management', 'Eine Plattform, ein Audit, eine Rechnung']
: ['DSGVO · NIS-2 · DORA · EU AI Act · ISO 27001 · BSI C5', 'EU-sovereign hosting and key-management', 'One platform, one audit, one bill'],
},
hub: {
tint: '#a78bfa', icon: '∞',
kicker: de ? 'Die Schleife' : 'The Loop',
title: de ? 'Compliance ↔ Code · Immer in Sync' : 'Compliance ↔ Code · Always in sync',
body: de
? 'Die Plattform ist eine einzige geschlossene Schleife. Jede Policy-Änderung fliesst in den Code; jede Code-Änderung fliesst in die Policy zurück.'
: 'The platform is a single closed loop. Every policy change ripples into code; every code change ripples back into policy. That\'s the USP in one diagram.',
bullets: de
? ['Single Source of Truth, zwei Oberflächen', 'Echtzeit-Sync, kein Batch-Abgleich', 'Auditoren, Entwickler und Sales fragen denselben Graphen ab']
: ['Single source of truth, two surfaces', 'Real-time sync, not batch reconciliation', 'Auditors, engineers and sales all query the same graph'],
},
}
}
// ── Pillar row ────────────────────────────────────────────────────────────────
function PillarRow({ side, title, body, tint, onClick, active, isLight }: {
side: 'left' | 'right'
title: string; body: string; tint: string
onClick: () => void; active: boolean; isLight: boolean
}) {
const [hover, setHover] = useState(false)
const lit = hover || active
const isLeft = side === 'left'
return (
<div
onClick={onClick}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
style={{
display: 'flex', alignItems: 'flex-start', gap: 12,
flexDirection: isLeft ? 'row-reverse' : 'row',
textAlign: isLeft ? 'right' : 'left',
padding: '10px 14px', borderRadius: 10, cursor: 'pointer',
transition: 'transform .25s, background .25s, box-shadow .25s',
background: lit
? `linear-gradient(${isLeft ? '270deg' : '90deg'}, ${tint}24 0%, ${tint}0a 70%, transparent 100%)`
: 'transparent',
boxShadow: lit
? `0 10px 30px ${tint}26, inset 0 0 0 1px ${tint}44`
: 'inset 0 0 0 1px transparent',
transform: lit ? (isLeft ? 'translateX(-3px)' : 'translateX(3px)') : 'translateX(0)',
}}
>
<div style={{
flex: '0 0 30px', width: 30, height: 30, borderRadius: 9,
background: lit ? `${tint}3a` : `${tint}22`,
border: `1px solid ${lit ? tint : tint + '66'}`,
display: 'flex', alignItems: 'center', justifyContent: 'center',
color: lit ? (isLight ? tint : '#fff') : tint, fontSize: 13, fontWeight: 700, marginTop: 2,
boxShadow: lit ? `0 0 14px ${tint}88, inset 0 1px 0 ${tint}80` : `inset 0 1px 0 ${tint}50`,
transition: 'all .25s',
}}></div>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{
fontSize: 13, fontWeight: 700,
color: isLight ? '#1a1a2e' : '#f7f5fc',
letterSpacing: -0.15, marginBottom: 3,
display: 'flex', alignItems: 'center', gap: 6,
justifyContent: isLeft ? 'flex-end' : 'flex-start',
}}>
<span>{title}</span>
<span style={{
fontSize: 10, color: tint, opacity: lit ? 1 : 0,
transform: `translateX(${lit ? 0 : (isLeft ? 4 : -4)}px)`,
transition: 'all .25s',
}}>{isLeft ? '' : ''}</span>
</div>
<div style={{
fontSize: 11, lineHeight: 1.55,
color: isLight
? `rgba(71,85,105,${lit ? 1 : .78})`
: `rgba(236,233,247,${lit ? .82 : .62})`,
transition: 'color .25s',
}}>{body}</div>
</div>
</div>
)
}
// ── Column header ─────────────────────────────────────────────────────────────
function ColHeader({ side, label, color, icon, sub, isLight }: {
side: 'left' | 'right'; label: string; color: string; icon: string; sub: string; isLight: boolean
}) {
const isLeft = side === 'left'
return (
<div style={{
display: 'flex', alignItems: 'center', gap: 10,
flexDirection: isLeft ? 'row-reverse' : 'row',
paddingBottom: 10, borderBottom: `1px solid ${color}35`,
}}>
<div style={{
width: 34, height: 34, borderRadius: 9,
background: `linear-gradient(135deg, ${color}55, ${color}20)`,
border: `1px solid ${color}88`,
display: 'flex', alignItems: 'center', justifyContent: 'center',
color: isLight ? color : '#fff', fontSize: 15, fontWeight: 700,
boxShadow: `0 0 18px ${color}55, inset 0 1px 0 ${color}aa`,
}}>{icon}</div>
<div>
<div style={{ fontSize: 18, fontWeight: 700, color: isLight ? '#1a1a2e' : '#f7f5fc', letterSpacing: -0.3, lineHeight: 1 }}>{label}</div>
<div style={{ ...MONO, fontSize: 9.5, letterSpacing: 2, color, opacity: .75, marginTop: 3, textTransform: 'uppercase' as const }}>{sub}</div>
</div>
</div>
)
}
// ── Central hub ───────────────────────────────────────────────────────────────
function CentralHub({ caption, isLight }: { caption: string; isLight: boolean }) {
return (
<div style={{ position: 'relative', width: 260, height: 320, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<div style={{
position: 'relative', width: 120, height: 120, borderRadius: '50%',
background: 'radial-gradient(circle at 32% 28%, #f0e9ff 0%, #c4aaff 26%, #7b5cd6 58%, #2a1560 100%)',
border: '1.5px solid rgba(216,202,255,.7)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
boxShadow: isLight
? '0 0 30px rgba(167,139,250,.4), 0 0 60px rgba(167,139,250,.15), inset 0 3px 0 rgba(255,255,255,.5), inset 0 -8px 14px rgba(0,0,0,.2)'
: '0 0 50px rgba(167,139,250,.65), 0 0 100px rgba(167,139,250,.25), inset 0 3px 0 rgba(255,255,255,.35), inset 0 -8px 14px rgba(0,0,0,.35)',
animation: isLight ? 'uspPulseLight 2.6s ease-in-out infinite' : 'uspPulse 2.6s ease-in-out infinite',
zIndex: 3,
}}>
<div style={{ position: 'absolute', inset: -14, borderRadius: '50%', border: `1px dashed ${isLight ? 'rgba(167,139,250,.5)' : 'rgba(216,202,255,.42)'}`, animation: 'uspSpin 14s linear infinite' }} />
<div style={{ position: 'absolute', inset: -30, borderRadius: '50%', border: `1px dashed ${isLight ? 'rgba(167,139,250,.3)' : 'rgba(216,202,255,.2)'}`, animation: 'uspSpin 22s linear infinite reverse' }} />
<svg width="54" height="26" viewBox="0 0 54 26" fill="none" stroke="#fff" strokeWidth="2.8" strokeLinecap="round" strokeLinejoin="round"
style={{ filter: 'drop-shadow(0 1px 3px rgba(0,0,0,.5))' }}>
<path d="M 10 13 C 10 5, 22 5, 27 13 C 32 21, 44 21, 44 13 C 44 5, 32 5, 27 13 C 22 21, 10 21, 10 13 Z" />
</svg>
</div>
<div style={{
position: 'absolute', left: 0, right: 0, bottom: 24, textAlign: 'center',
...MONO, fontSize: 9.5, letterSpacing: 2.5,
color: isLight ? 'rgba(109,77,194,.75)' : 'rgba(216,202,255,.75)',
textTransform: 'uppercase' as const, fontWeight: 600,
}}>{caption}</div>
</div>
)
}
// ── Bridge SVG connectors ─────────────────────────────────────────────────────
function BridgeConnectors({ isLight }: { isLight: boolean }) {
const rfpY = 130
const sub2Y = 250
const hubCx = 500
const hubR = 72
return (
<svg viewBox="0 0 1000 400" preserveAspectRatio="none"
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', pointerEvents: 'none', zIndex: 1 }}>
<defs>
<linearGradient id="uspFromL" x1="0" x2="1">
<stop offset="0" stopColor="#a78bfa" stopOpacity="0" />
<stop offset=".3" stopColor="#a78bfa" stopOpacity={isLight ? '.6' : '.85'} />
<stop offset="1" stopColor="#c084fc" stopOpacity={isLight ? '.2' : '.3'} />
</linearGradient>
<linearGradient id="uspToR" x1="0" x2="1">
<stop offset="0" stopColor="#c084fc" stopOpacity={isLight ? '.2' : '.3'} />
<stop offset=".7" stopColor="#fbbf24" stopOpacity={isLight ? '.6' : '.85'} />
<stop offset="1" stopColor="#fbbf24" stopOpacity="0" />
</linearGradient>
</defs>
<line x1="40" y1={rfpY} x2={hubCx - hubR} y2={rfpY}
stroke="url(#uspFromL)" strokeWidth="2" strokeDasharray="4 5"
style={{ animation: 'uspFlowR 1.6s linear infinite' }} />
<line x1={hubCx + hubR} y1={rfpY} x2="960" y2={rfpY}
stroke="url(#uspToR)" strokeWidth="2" strokeDasharray="4 5"
style={{ animation: 'uspFlowR 1.6s linear infinite' }} />
<line x1="40" y1={sub2Y} x2={hubCx - hubR} y2={sub2Y}
stroke="url(#uspFromL)" strokeWidth="2" strokeDasharray="4 5"
style={{ animation: 'uspFlowR 1.6s linear infinite' }} />
<line x1={hubCx + hubR} y1={sub2Y} x2="960" y2={sub2Y}
stroke="url(#uspToR)" strokeWidth="2" strokeDasharray="4 5"
style={{ animation: 'uspFlowR 1.6s linear infinite' }} />
{([rfpY, sub2Y] as number[]).map(y => (
<g key={y}>
<circle cx={hubCx - hubR} cy={y} r="4" fill={isLight ? '#eef2ff' : '#1a0f34'} stroke="#a78bfa" strokeWidth="1.2" />
<circle cx={hubCx - hubR} cy={y} r="1.5" fill="#a78bfa" />
<circle cx={hubCx + hubR} cy={y} r="4" fill={isLight ? '#eef2ff' : '#1a0f34'} stroke="#fbbf24" strokeWidth="1.2" />
<circle cx={hubCx + hubR} cy={y} r="1.5" fill="#fbbf24" />
</g>
))}
<circle r="3" fill="#c4aaff" style={{ filter: 'drop-shadow(0 0 6px #a78bfa)' }}>
<animate attributeName="cx" from="40" to="960" dur="3.5s" repeatCount="indefinite" />
<animate attributeName="cy" values={`${rfpY};${rfpY}`} dur="3.5s" repeatCount="indefinite" />
</circle>
<circle r="3" fill="#fde68a" style={{ filter: 'drop-shadow(0 0 6px #fbbf24)' }}>
<animate attributeName="cx" from="960" to="40" dur="3.5s" repeatCount="indefinite" />
<animate attributeName="cy" values={`${sub2Y};${sub2Y}`} dur="3.5s" repeatCount="indefinite" />
</circle>
</svg>
)
}
// ── Under-the-hood feature card ───────────────────────────────────────────────
function FeatureCard({ icon, title, body, tint, Ticker, onClick, active, isLight }: {
icon: string; title: string; body: string; tint: string
Ticker: React.ComponentType<{ tint: string; isLight: boolean }>
onClick: () => void; active: boolean; isLight: boolean
}) {
const [hover, setHover] = useState(false)
const lit = hover || active
return (
<div
onClick={onClick}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
style={{
position: 'relative', padding: '13px 15px',
background: isLight
? lit
? `linear-gradient(180deg, ${tint}18 0%, ${tint}08 55%, rgba(248,250,252,.95) 100%)`
: 'linear-gradient(180deg, #ffffff, #f8fafc)'
: `linear-gradient(180deg, ${tint}${lit ? '2a' : '1a'} 0%, ${tint}07 55%, rgba(14,8,28,.85) 100%)`,
border: `1px solid ${lit ? tint : isLight ? 'rgba(0,0,0,.1)' : tint + '4a'}`,
borderRadius: 12,
boxShadow: lit
? `0 18px 40px ${tint}33, 0 0 0 1px ${tint}66, inset 0 1px 0 ${tint}60`
: isLight
? '0 2px 8px rgba(0,0,0,.08), inset 0 1px 0 rgba(255,255,255,.8)'
: `0 10px 24px rgba(0,0,0,.4), inset 0 1px 0 ${tint}35`,
minWidth: 0, cursor: 'pointer',
transform: lit ? 'translateY(-3px)' : 'translateY(0)',
transition: 'transform .25s, box-shadow .25s, background .25s, border-color .25s',
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>
<span style={{
width: 22, height: 22, borderRadius: 6,
background: lit ? `${tint}44` : `${tint}22`,
border: `1px solid ${lit ? tint : tint + '66'}`,
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
color: lit ? (isLight ? tint : '#fff') : tint, fontSize: 12,
boxShadow: lit ? `0 0 12px ${tint}88` : 'none',
transition: 'all .25s',
}}>{icon}</span>
<span style={{ fontSize: 12.5, fontWeight: 700, color: isLight ? '#1a1a2e' : '#f7f5fc', letterSpacing: -0.15, flex: 1 }}>{title}</span>
<span style={{ fontSize: 10, color: tint, opacity: lit ? 1 : 0.5, transform: `translateX(${lit ? 0 : -3}px)`, transition: 'all .25s' }}></span>
</div>
<div style={{
fontSize: 11, lineHeight: 1.45,
color: isLight
? `rgba(71,85,105,${lit ? 1 : .78})`
: `rgba(236,233,247,${lit ? .82 : .65})`,
transition: 'color .25s',
}}>{body}</div>
<Ticker tint={tint} isLight={isLight} />
</div>
)
}
// ── Detail modal ──────────────────────────────────────────────────────────────
function DetailModal({ item, onClose, isLight }: { item: DetailItem | null; onClose: () => void; isLight: boolean }) {
useEffect(() => {
if (!item) return
const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose() }
window.addEventListener('keydown', onKey)
return () => window.removeEventListener('keydown', onKey)
}, [item, onClose])
return (
<AnimatePresence>
{item && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
onClick={onClose}
style={{
position: 'absolute', inset: 0, zIndex: 50,
background: isLight ? 'rgba(240,244,255,.72)' : 'rgba(5,2,16,.72)',
backdropFilter: 'blur(6px)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
}}
>
<motion.div
initial={{ opacity: 0, scale: 0.94 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.94 }}
transition={{ duration: 0.22 }}
onClick={e => e.stopPropagation()}
style={{
width: 560, maxWidth: '88%',
background: isLight
? `linear-gradient(180deg, ${item.tint}10 0%, rgba(255,255,255,.98) 50%, rgba(248,250,252,.99) 100%)`
: `linear-gradient(180deg, ${item.tint}18 0%, rgba(20,10,40,.96) 50%, rgba(14,8,28,.98) 100%)`,
border: `1px solid ${item.tint}${isLight ? '44' : '66'}`,
borderRadius: 16,
boxShadow: isLight
? `0 20px 60px rgba(0,0,0,.12), 0 0 40px ${item.tint}18, inset 0 1px 0 rgba(255,255,255,.9)`
: `0 30px 80px rgba(0,0,0,.6), 0 0 60px ${item.tint}33, inset 0 1px 0 ${item.tint}55`,
padding: '22px 26px',
color: isLight ? '#1a1a2e' : '#ece9f7',
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 14 }}>
<div style={{
width: 38, height: 38, borderRadius: 10,
background: `linear-gradient(135deg, ${item.tint}66, ${item.tint}22)`,
border: `1px solid ${item.tint}`,
display: 'flex', alignItems: 'center', justifyContent: 'center',
color: isLight ? item.tint : '#fff', fontSize: 16, fontWeight: 700,
boxShadow: `0 0 18px ${item.tint}66`,
}}>{item.icon}</div>
<div style={{ flex: 1 }}>
<div style={{ ...MONO, fontSize: 9.5, letterSpacing: 2.5, color: item.tint, textTransform: 'uppercase' as const, fontWeight: 600, marginBottom: 2 }}>
{item.kicker}
</div>
<div style={{ fontSize: 19, fontWeight: 700, color: isLight ? '#1a1a2e' : '#f7f5fc', letterSpacing: -0.3 }}>{item.title}</div>
</div>
<button onClick={onClose} style={{
background: 'transparent', border: `1px solid ${item.tint}55`,
borderRadius: 8, cursor: 'pointer', width: 30, height: 30,
display: 'flex', alignItems: 'center', justifyContent: 'center',
color: isLight ? '#64748b' : 'rgba(236,233,247,.6)',
}}>
<X style={{ width: 14, height: 14 }} />
</button>
</div>
<div style={{ fontSize: 13, lineHeight: 1.6, color: isLight ? '#475569' : 'rgba(236,233,247,.82)', marginBottom: 16 }}>
{item.body}
</div>
{item.bullets && (
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 14 }}>
{item.bullets.map((b, i) => (
<div key={i} style={{
display: 'flex', alignItems: 'flex-start', gap: 10,
padding: '8px 12px', borderRadius: 8,
background: isLight ? 'rgba(0,0,0,.04)' : 'rgba(0,0,0,.3)',
border: `1px solid ${item.tint}${isLight ? '22' : '33'}`,
}}>
<span style={{ color: item.tint, fontSize: 12, marginTop: 1 }}></span>
<span style={{ fontSize: 12, lineHeight: 1.5, color: isLight ? '#475569' : 'rgba(236,233,247,.78)' }}>{b}</span>
</div>
))}
</div>
)}
{item.stat && (
<div style={{
...MONO, padding: '10px 14px', borderRadius: 8,
background: isLight ? 'rgba(0,0,0,.04)' : 'rgba(0,0,0,.45)',
border: `1px solid ${item.tint}${isLight ? '33' : '55'}`,
fontSize: 12, color: isLight ? '#475569' : 'rgba(236,233,247,.9)',
display: 'flex', alignItems: 'center', gap: 10,
}}>
<span style={{ color: isLight ? '#16a34a' : '#4ade80' }}></span>
<span style={{ color: item.tint }}>{item.stat.k}</span>
<span style={{ color: isLight ? '#1a1a2e' : '#f5f3fc', fontWeight: 600 }}>{item.stat.v}</span>
</div>
)}
</motion.div>
</motion.div>
)}
</AnimatePresence>
)
}
// ── Star field ────────────────────────────────────────────────────────────────
function StarField({ isLight }: { isLight: boolean }) {
const stars = useMemo(() => {
let s = 41
const r = () => { s = (s * 9301 + 49297) % 233280; return s / 233280 }
return Array.from({ length: 90 }, () => ({ x: r() * 100, y: r() * 100, size: r() * 1.4 + 0.3, op: r() * 0.5 + 0.15 }))
}, [])
if (isLight) return null
return (
<div style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>
{stars.map((st, i) => (
<div key={i} style={{
position: 'absolute', left: `${st.x}%`, top: `${st.y}%`,
width: st.size, height: st.size, borderRadius: '50%',
background: '#fff', opacity: st.op,
boxShadow: `0 0 ${st.size * 3}px rgba(180,160,255,.7)`,
}} />
))}
</div>
)
}
// ── Main slide ────────────────────────────────────────────────────────────────
export default function USPSlide({ lang }: USPSlideProps) {
const de = lang === 'de'
const isLight = useIsLight()
const details = getDetails(de)
const [detail, setDetail] = useState<DetailItem | null>(null)
const open = (k: string) => setDetail(details[k])
const close = () => setDetail(null)
return (
<div>
<style>{CSS_KF}</style>
<FadeInView className="text-center mb-1">
<h2 className="text-5xl md:text-6xl font-bold mb-1">
<GradientText>USP</GradientText>
</h2>
<p className="text-lg text-white/50 max-w-2xl mx-auto">
{de ? 'Compliance und Code — in einer Plattform, immer synchron' : 'Compliance and code — one platform, always in sync'}
</p>
</FadeInView>
<FadeInView delay={0.1}>
{/* ── MAIN CANVAS ───────────────────────────────────────────────── */}
<div style={{
position: 'relative', overflow: 'hidden', borderRadius: 16,
background: isLight
? 'linear-gradient(160deg, #f0f4ff 0%, #eff6ff 50%, #f5f0ff 100%)'
: 'radial-gradient(ellipse at 50% 30%, #1a0f34 0%, #0e0720 55%, #050210 100%)',
color: isLight ? '#1a1a2e' : '#ece9f7',
fontFamily: '"Inter", system-ui, sans-serif',
WebkitFontSmoothing: 'antialiased',
transform: 'scale(1.12)',
transformOrigin: 'top center',
marginBottom: -40,
}}>
{/* Ambient glow — dark only */}
{!isLight && (
<div style={{
position: 'absolute', top: -120, left: '50%', transform: 'translateX(-50%)',
width: 800, height: 500, borderRadius: '50%',
background: 'radial-gradient(ellipse, rgba(167,139,250,.2), transparent 65%)',
filter: 'blur(50px)', pointerEvents: 'none',
}} />
)}
<StarField isLight={isLight} />
{/* Interaction hint */}
<div style={{
position: 'absolute', top: 20, right: 28, zIndex: 5,
...MONO, fontSize: 9.5, letterSpacing: 2,
color: isLight ? 'rgba(109,77,194,.6)' : 'rgba(167,139,250,.55)',
textTransform: 'uppercase', fontWeight: 600,
display: 'flex', alignItems: 'center', gap: 6,
}}>
<span style={{ display: 'inline-block', width: 6, height: 6, borderRadius: '50%', background: isLight ? '#16a34a' : '#4ade80', boxShadow: `0 0 8px ${isLight ? '#16a34a' : '#4ade80'}` }} />
{de ? 'Element anklicken' : 'Click any element'}
</div>
{/* Bridge */}
<div style={{ position: 'relative', margin: '8px 48px 0', height: 330 }}>
<BridgeConnectors isLight={isLight} />
<div style={{
position: 'relative', zIndex: 2,
display: 'grid', gridTemplateColumns: '1fr 260px 1fr', gap: 0,
alignItems: 'start', height: '100%',
}}>
{/* LEFT — Compliance */}
<div style={{ display: 'flex', flexDirection: 'column', paddingRight: 20 }}>
<div style={{ height: 40, marginBottom: 36 }}>
<ColHeader side="left" label="Compliance" color="#a78bfa" icon="⎈" sub="policy · audit · proof" isLight={isLight} />
</div>
<div style={{ height: 110, display: 'flex', alignItems: 'center' }}>
<div style={{ width: '100%' }}>
<PillarRow side="left" tint="#a78bfa" isLight={isLight}
title={de ? 'RFQ-Prüfung' : 'RFQ Verification'}
body={de
? 'Kunden-Anforderungsdokumente automatisch gegen aktuellen Code validiert. Abweichungen erkannt, Änderungen vorgeschlagen.'
: 'Customer requirements auto-validated against current code. Gaps surfaced with implementation proposals.'}
onClick={() => open('rfq')}
active={detail?.title === details.rfq.title}
/>
</div>
</div>
<div style={{ height: 110, display: 'flex', alignItems: 'center' }}>
<div style={{ width: '100%' }}>
<PillarRow side="left" tint="#c084fc" isLight={isLight}
title={de ? 'Prozess-Compliance' : 'Process Compliance'}
body={de
? 'Vom Audit-Finding bis zur Code-Änderung vollständig automatisiert. Rollen, Fristen, Eskalation — End-to-End verwaltet.'
: 'From audit finding to code change, fully automated. Roles, deadlines, escalation — managed end-to-end.'}
onClick={() => open('process')}
active={detail?.title === details.process.title}
/>
</div>
</div>
</div>
{/* CENTER hub */}
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' }}>
<div
onClick={() => open('hub')}
style={{ cursor: 'pointer', transition: 'transform .25s, filter .25s' }}
onMouseEnter={e => { (e.currentTarget as HTMLDivElement).style.transform = 'scale(1.05)'; (e.currentTarget as HTMLDivElement).style.filter = 'brightness(1.15)' }}
onMouseLeave={e => { (e.currentTarget as HTMLDivElement).style.transform = 'scale(1)'; (e.currentTarget as HTMLDivElement).style.filter = 'brightness(1)' }}
>
<CentralHub caption={de ? 'Immer in Sync' : 'Always in sync'} isLight={isLight} />
</div>
</div>
{/* RIGHT — Code */}
<div style={{ display: 'flex', flexDirection: 'column', paddingLeft: 20 }}>
<div style={{ height: 40, marginBottom: 36 }}>
<ColHeader side="right" label="Code" color="#fbbf24" icon="⟨/⟩" sub="sast · dast · sbom" isLight={isLight} />
</div>
<div style={{ height: 110, display: 'flex', alignItems: 'center' }}>
<div style={{ width: '100%' }}>
<PillarRow side="right" tint="#fbbf24" isLight={isLight}
title={de ? 'Bidirektional' : 'Bidirectional Sync'}
body={de
? 'Compliance-Änderungen fliessen in Code; Code-Änderungen aktualisieren Docs. Beide Seiten immer synchron — kein Informationsverlust.'
: 'Compliance edits flow into code; code changes update docs. Both sides always in sync — zero drift.'}
onClick={() => open('bidir')}
active={detail?.title === details.bidir.title}
/>
</div>
</div>
<div style={{ height: 110, display: 'flex', alignItems: 'center' }}>
<div style={{ width: '100%' }}>
<PillarRow side="right" tint="#f59e0b" isLight={isLight}
title={de ? 'Kontinuierlich' : 'Continuous, Not Yearly'}
body={de
? 'Klassische Audits einmal jährlich — wir prüfen bei jedem Commit. Findings werden sofort zu Tickets mit konkreten Fixes.'
: 'Classic audits run once a year. We run on every commit — findings become tickets with fixes ready.'}
onClick={() => open('cont')}
active={detail?.title === details.cont.title}
/>
</div>
</div>
</div>
</div>
</div>
{/* Under the Hood */}
<div style={{ position: 'relative', zIndex: 2, padding: '0 48px 16px' }}>
<div style={{
...MONO, fontSize: 9.5, letterSpacing: 3.5,
color: isLight ? 'rgba(109,77,194,.7)' : 'rgba(167,139,250,.7)',
textTransform: 'uppercase', fontWeight: 600, textAlign: 'center', marginBottom: 12,
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 14,
}}>
<span style={{ width: 80, height: 1, background: isLight ? 'linear-gradient(90deg, transparent, rgba(109,77,194,.4))' : 'linear-gradient(90deg, transparent, rgba(167,139,250,.5))' }} />
{de ? 'Unter der Haube' : 'Under the Hood'}
<span style={{ width: 80, height: 1, background: isLight ? 'linear-gradient(270deg, transparent, rgba(109,77,194,.4))' : 'linear-gradient(270deg, transparent, rgba(167,139,250,.5))' }} />
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 12 }}>
<FeatureCard tint="#a78bfa" icon="⇄" isLight={isLight}
title={de ? 'End-to-End Rückverfolgbarkeit' : 'End-to-End Traceability'}
body={de
? 'Regulatorische Anforderungen deterministisch mit Code und System verknüpft — inklusive revisionssicherem Evidence-Layer.'
: 'Regulatory requirements deterministically linked to system state and code — including audit-proof evidence layer.'}
onClick={() => open('trace')}
active={detail?.title === details.trace.title}
Ticker={TickTrace}
/>
<FeatureCard tint="#c084fc" icon="◉" isLight={isLight}
title={de ? 'Continuous Compliance Engine' : 'Continuous Compliance Engine'}
body={de
? 'Automatische Audit-Validierung bei jeder Änderung — Code, Infrastruktur, Prozesse.'
: 'Automatic audit validation on every change — code, infrastructure, processes.'}
onClick={() => open('engine')}
active={detail?.title === details.engine.title}
Ticker={TickEngine}
/>
<FeatureCard tint="#fbbf24" icon="✦" isLight={isLight}
title={de ? 'Compliance Optimizer' : 'Compliance Optimizer'}
body={de
? 'Maximale Compliance pro €. Findet den Sweet Spot zwischen Geschwindigkeit und Risiko.'
: 'Maximally-compliant per €. Finds the sweet spot between speed & risk.'}
onClick={() => open('opt')}
active={detail?.title === details.opt.title}
Ticker={TickOptimizer}
/>
<FeatureCard tint="#f59e0b" icon="◎" isLight={isLight}
title={de ? 'EU-Trust & Governance' : 'EU Trust & Governance'}
body={de
? 'DSGVO, NIS-2, DORA, EU AI Act — eine Plattform, EU-souverän.'
: 'DSGVO, NIS-2, DORA, EU AI Act — one platform, EU-sovereign.'}
onClick={() => open('stack')}
active={detail?.title === details.stack.title}
Ticker={TickStack}
/>
</div>
</div>
{/* MOAT card */}
<div style={{
position: 'relative', zIndex: 2, margin: '0 48px 20px',
padding: '14px 20px', borderRadius: 12,
background: isLight
? 'linear-gradient(90deg, rgba(251,191,36,.1), rgba(99,102,241,.06))'
: 'linear-gradient(90deg, rgba(251,191,36,.1), rgba(99,102,241,.08))',
border: `1px solid ${isLight ? 'rgba(251,191,36,.25)' : 'rgba(251,191,36,.2)'}`,
display: 'flex', alignItems: 'center', gap: 14,
}}>
<div style={{
width: 44, height: 44, borderRadius: 12, flexShrink: 0,
background: 'linear-gradient(135deg, #f59e0b, #ea580c)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
boxShadow: '0 4px 16px rgba(245,158,11,.3)',
fontSize: 20, fontWeight: 800, color: '#fff',
}}></div>
<div>
<div style={{ fontSize: 15, fontWeight: 700, color: '#fbbf24', marginBottom: 3 }}>MOAT</div>
<div style={{ fontSize: 12.5, lineHeight: 1.5, color: isLight ? '#475569' : 'rgba(236,233,247,.82)' }}>
{de
? 'Jeder kann sagen, was verboten ist. Kaum jemand kann sagen, wie weit du maximal gehen kannst. Das ist unser Produkt.'
: 'Anyone can say what\'s forbidden. Almost no one can tell you how far you can actually go. That\'s our product.'}
</div>
</div>
</div>
<DetailModal item={detail} onClose={close} isLight={isLight} />
</div>
</FadeInView>
</div>
)
}
+5 -5
View File
@@ -6,7 +6,7 @@ import AnimatedCounter from './AnimatedCounter'
interface KPICardProps {
label: string
value: number
value: number | string
prefix?: string
suffix?: string
decimals?: number
@@ -40,10 +40,10 @@ export default function KPICard({
style={{ backgroundColor: color }}
/>
<p className="text-[10px] uppercase tracking-wider text-white/40 mb-1">{label}</p>
<div className="flex items-end gap-2">
<p className="text-[10px] uppercase tracking-wider text-white/40 mb-1 text-center">{label}</p>
<div className="flex items-end justify-center gap-2">
<p className="text-2xl font-bold text-white leading-none">
<AnimatedCounter target={value} prefix={prefix} suffix={suffix} duration={1200} decimals={decimals} />
{typeof value === 'string' ? `${prefix}${value}${suffix}` : <AnimatedCounter target={value} prefix={prefix} suffix={suffix} duration={1200} decimals={decimals} />}
</p>
{trend !== 'neutral' && (
<span className={`flex items-center gap-0.5 text-xs pb-0.5 ${trend === 'up' ? 'text-emerald-400' : 'text-red-400'}`}>
@@ -52,7 +52,7 @@ export default function KPICard({
)}
</div>
{subLabel && (
<p className="text-[10px] text-white/30 mt-1">{subLabel}</p>
<p className="text-[10px] text-white/30 mt-1 text-center">{subLabel}</p>
)}
</motion.div>
)
@@ -0,0 +1,20 @@
'use client'
import { Language } from '@/lib/types'
interface ProjectionFooterProps {
lang: Language
}
export default function ProjectionFooter({ lang }: ProjectionFooterProps) {
const de = lang === 'de'
return (
<div className="mt-3 pt-2 border-t border-white/5">
<p className="text-xs text-white/30 text-center italic">
{de
? 'Alle Finanzdaten sind Planzahlen und stellen keine Garantie für künftige Ergebnisse dar (Stand: Q2 2026)'
: 'All financial data are projections and do not constitute a guarantee of future results (as of: Q2 2026)'}
</p>
</div>
)
}
+30 -3
View File
@@ -1,11 +1,11 @@
import { SignJWT, jwtVerify } from 'jose'
import { SignJWT, jwtVerify, decodeJwt } from 'jose'
import { randomBytes, createHash } from 'crypto'
import { cookies } from 'next/headers'
import pool from './db'
const COOKIE_NAME = 'pitch_session'
const JWT_EXPIRY = '1h'
const SESSION_EXPIRY_HOURS = 24
const JWT_EXPIRY = `${SESSION_EXPIRY_HOURS}h`
function getJwtSecret() {
const secret = process.env.PITCH_JWT_SECRET
@@ -125,7 +125,34 @@ export async function getSessionFromCookie(): Promise<JwtPayload | null> {
const cookieStore = await cookies()
const token = cookieStore.get(COOKIE_NAME)?.value
if (!token) return null
return verifyJwt(token)
// Fast path: valid non-expired JWT
const payload = await verifyJwt(token)
if (payload) return payload
// Slow path: JWT may be expired but DB session could still be valid.
// Decode without signature/expiry check to recover sessionId + sub.
try {
const decoded = decodeJwt(token) as Partial<JwtPayload>
if (!decoded.sessionId || !decoded.sub) return null
const valid = await validateSession(decoded.sessionId, decoded.sub)
if (!valid) return null
// DB session still live — fetch email and reissue a fresh JWT
const { rows } = await pool.query(
`SELECT email FROM pitch_investors WHERE id = $1`,
[decoded.sub]
)
if (rows.length === 0) return null
const freshJwt = await createJwt({ sub: decoded.sub, email: rows[0].email, sessionId: decoded.sessionId })
await setSessionCookie(freshJwt)
return { sub: decoded.sub, email: rows[0].email, sessionId: decoded.sessionId }
} catch {
return null
}
}
export function getClientIp(request: Request): string | null {
+8 -1
View File
@@ -1,4 +1,11 @@
import { Pool } from 'pg'
import { Pool, types } from 'pg'
// Coerce NUMERIC/DECIMAL columns to JS numbers globally. The default
// node-postgres behavior hands them back as strings, which silently breaks
// every downstream `.toFixed()` / direct-number call (seen crashing
// UnitEconomicsCards on the Finanzen slide). Our values fit comfortably in
// Number.MAX_SAFE_INTEGER, so the precision trade-off is fine.
types.setTypeParser(types.builtins.NUMERIC, (val) => (val === null ? null : parseFloat(val)))
const pool = new Pool({
connectionString: process.env.DATABASE_URL || 'postgres://breakpilot:breakpilot123@localhost:5432/breakpilot_db',
+9
View File
@@ -0,0 +1,9 @@
export const DEFAULT_GREETING = 'Sehr geehrter Herr'
export const DEFAULT_MESSAGE =
'wir freuen uns sehr, Ihnen exklusiven Zugang zum interaktiven Investor Pitch Deck unserer geplanten Unternehmung <strong>BreakPilot</strong> zu ermöglichen.<br><br>BreakPilot adressiert eine zentrale Herausforderung moderner Softwareentwicklung: die kontinuierliche Einhaltung regulatorischer Anforderungen bei gleichzeitig hoher Entwicklungsgeschwindigkeit.<br><br>Unsere geplante Lösung verbindet Code Security, automatisierte Compliance und regulatorische Intelligenz in einer durchgängigen Plattform. Ziel ist es, Unternehmen nicht nur Abweichungen aufzuzeigen, sondern konkret zu steuern, wie Systeme maximal regelkonform und gleichzeitig optimal genutzt werden können — insbesondere im Kontext von KI-Systemen und dem EU AI Act.<br><br>Das Pitch Deck gibt Ihnen einen strukturierten Einblick in Problemraum, Lösungsarchitektur sowie unsere geplante Finanzierungsstrategie.'
export const DEFAULT_CLOSING =
'Gerne stehen wir Ihnen für einen persönlichen Austausch oder eine vertiefende Diskussion jederzeit zur Verfügung.\n\nMit freundlichen Grüßen,\nBenjamin Bönisch & Sharang Parnerkar\nGründer — BreakPilot'
export function getDefaultGreeting(name: string | null): string {
return name ? `Sehr geehrter Herr ${name}` : DEFAULT_GREETING
}
+94 -21
View File
@@ -1,4 +1,12 @@
import 'server-only'
import nodemailer from 'nodemailer'
import {
DEFAULT_MESSAGE,
DEFAULT_CLOSING,
getDefaultGreeting,
} from '@/lib/email-templates'
export { DEFAULT_GREETING, DEFAULT_MESSAGE, DEFAULT_CLOSING, getDefaultGreeting } from '@/lib/email-templates'
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
@@ -16,14 +24,21 @@ const fromAddr = process.env.SMTP_FROM_ADDR || 'noreply@breakpilot.ai'
export async function sendMagicLinkEmail(
to: string,
investorName: string | null,
magicLinkUrl: string
magicLinkUrl: string,
greeting?: string,
message?: string,
closing?: string,
): Promise<void> {
const greeting = investorName ? `Hello ${investorName}` : 'Hello'
const effectiveGreeting = greeting || getDefaultGreeting(investorName)
const effectiveMessage = message || DEFAULT_MESSAGE
const effectiveClosing = closing || DEFAULT_CLOSING
const closingHtml = effectiveClosing.replace(/\n/g, '<br>')
const ttl = process.env.MAGIC_LINK_TTL_HOURS || '72'
await transporter.sendMail({
from: `"${fromName}" <${fromAddr}>`,
to,
subject: 'Your BreakPilot Pitch Deck Access',
subject: 'BreakPilot ComplAI — Ihr persönlicher Pitch-Deck-Zugang',
html: `
<!DOCTYPE html>
<html>
@@ -36,6 +51,8 @@ export async function sendMagicLinkEmail(
<tr>
<td align="center">
<table width="560" cellpadding="0" cellspacing="0" style="background:#111127;border-radius:12px;border:1px solid rgba(99,102,241,0.2);">
<!-- Header -->
<tr>
<td style="padding:40px 40px 20px;">
<h1 style="margin:0;font-size:24px;color:#e0e0ff;font-weight:600;">
@@ -46,40 +63,96 @@ export async function sendMagicLinkEmail(
</p>
</td>
</tr>
<!-- Greeting + Message -->
<tr>
<td style="padding:20px 40px;">
<td style="padding:20px 40px 0;">
<p style="margin:0 0 16px;font-size:16px;color:rgba(255,255,255,0.8);line-height:1.6;">
${greeting},
${effectiveGreeting},
</p>
<p style="margin:0 0 24px;font-size:16px;color:rgba(255,255,255,0.8);line-height:1.6;">
You have been invited to view the BreakPilot ComplAI investor pitch deck.
Click the button below to access the interactive presentation.
<p style="margin:0 0 24px;font-size:15px;color:rgba(255,255,255,0.7);line-height:1.7;">
${effectiveMessage}
</p>
<table cellpadding="0" cellspacing="0" style="margin:0 0 24px;">
</td>
</tr>
<!-- Magic Link Explanation -->
<tr>
<td style="background:linear-gradient(135deg,#6366f1,#8b5cf6);border-radius:8px;padding:14px 32px;">
<td style="padding:0 40px 20px;">
<div style="background:rgba(99,102,241,0.08);border:1px solid rgba(99,102,241,0.15);border-radius:8px;padding:16px;">
<p style="margin:0 0 4px;font-size:11px;font-weight:600;color:rgba(99,102,241,0.8);text-transform:uppercase;letter-spacing:0.5px;">
Ihr persönlicher Zugang
</p>
<p style="margin:0;font-size:13px;color:rgba(255,255,255,0.5);line-height:1.5;">
Der folgende Link ist individuell für Sie generiert, einmalig nutzbar und aus Sicherheitsgründen für ${ttl} Stunden gültig. Er gewährt Ihnen Zugang zu unserem interaktiven Pitch Deck inklusive integriertem KI-Assistenten für weiterführende Fragen.
</p>
</div>
</td>
</tr>
<!-- Button -->
<tr>
<td style="padding:0 40px 16px;" align="center">
<table cellpadding="0" cellspacing="0">
<tr>
<td style="background:linear-gradient(135deg,#6366f1,#8b5cf6);border-radius:8px;padding:14px 40px;">
<a href="${magicLinkUrl}" style="color:#ffffff;font-size:16px;font-weight:600;text-decoration:none;display:inline-block;">
View Pitch Deck
Pitch Deck öffnen
</a>
</td>
</tr>
</table>
<p style="margin:0 0 8px;font-size:13px;color:rgba(255,255,255,0.4);line-height:1.5;">
This link expires in ${process.env.MAGIC_LINK_TTL_HOURS || '72'} hours and can only be used once.
</p>
<p style="margin:0;font-size:13px;color:rgba(255,255,255,0.3);line-height:1.5;word-break:break-all;">
${magicLinkUrl}
</p>
</td>
</tr>
<!-- Raw link -->
<tr>
<td style="padding:20px 40px 40px;border-top:1px solid rgba(255,255,255,0.05);">
<p style="margin:0;font-size:12px;color:rgba(255,255,255,0.25);line-height:1.5;">
If you did not expect this email, you can safely ignore it.
This is an AI-first company we don't do PDFs.
<td style="padding:0 40px 24px;">
<p style="margin:0;font-size:11px;color:rgba(255,255,255,0.25);line-height:1.5;word-break:break-all;">
Falls der Button nicht funktioniert: ${magicLinkUrl}
</p>
</td>
</tr>
<!-- Closing -->
<tr>
<td style="padding:0 40px 24px;">
<p style="margin:0;font-size:15px;color:rgba(255,255,255,0.7);line-height:1.7;">
${closingHtml}
</p>
</td>
</tr>
<!-- Legal Footer DE -->
<tr>
<td style="padding:20px 40px 12px;border-top:1px solid rgba(255,255,255,0.05);">
<p style="margin:0 0 8px;font-size:10px;font-weight:600;color:rgba(255,255,255,0.3);text-transform:uppercase;letter-spacing:0.5px;">
Vertraulichkeit &amp; Haftungsausschluss
</p>
<p style="margin:0 0 6px;font-size:10px;color:rgba(255,255,255,0.18);line-height:1.5;">
Dieses Pitch Deck wurde im Zusammenhang mit einer geplanten Unternehmung erstellt und dient ausschließlich Informationszwecken. Es ist vertraulich und wurde ausschließlich für den namentlich eingeladenen Empfänger bereitgestellt. Durch das Öffnen des Links erklärt sich der Empfänger einverstanden: (a) Der Inhalt ist vertraulich zu behandeln und darf nicht an Dritte weitergegeben, kopiert oder zugänglich gemacht werden. Ausgenommen sind Berater (Rechtsanwälte, Steuerberater), die berufsrechtlich zur Verschwiegenheit verpflichtet sind. (b) Die Informationen dürfen ausschließlich zur Bewertung einer möglichen zukünftigen Beteiligung verwendet werden. (c) Diese Vertraulichkeitsverpflichtung gilt für drei (3) Jahre ab Übermittlung, unabhängig davon, ob eine Beteiligung zustande kommt.
</p>
<p style="margin:0 0 12px;font-size:10px;color:rgba(255,255,255,0.18);line-height:1.5;">
Dieses Dokument stellt weder ein Angebot zum Verkauf noch eine Aufforderung zur Abgabe eines Angebots zum Erwerb von Wertpapieren dar. Es handelt sich nicht um einen Wertpapierprospekt. Alle dargestellten Inhalte beziehen sich auf eine geplante Unternehmung und können sich im weiteren Verlauf ändern. Finanzangaben sind Planzahlen und stellen keine Garantie für zukünftige Ergebnisse dar. Eine Beteiligung an einem jungen Vorhaben ist mit erheblichen Risiken verbunden, einschließlich des Risikos eines Totalverlusts. Es gilt deutsches Recht. Gerichtsstand ist Konstanz, Deutschland.
</p>
</td>
</tr>
<!-- Legal Footer EN -->
<tr>
<td style="padding:0 40px 40px;">
<p style="margin:0 0 8px;font-size:10px;font-weight:600;color:rgba(255,255,255,0.2);text-transform:uppercase;letter-spacing:0.5px;">
Confidentiality &amp; Disclaimer
</p>
<p style="margin:0 0 6px;font-size:10px;color:rgba(255,255,255,0.13);line-height:1.5;">
This pitch deck has been prepared in the context of a planned venture and is provided for informational purposes only. It is confidential and has been shared exclusively with the personally invited recipient. By opening this link, the recipient agrees: (a) The content must be treated confidentially and may not be disclosed, copied or made accessible to third parties. Excluded are advisors (lawyers, tax advisors) professionally bound to secrecy. (b) The information may only be used for evaluating a potential future investment. (c) This confidentiality obligation applies for three (3) years from transmission, regardless of whether a participation materializes.
</p>
<p style="margin:0;font-size:10px;color:rgba(255,255,255,0.13);line-height:1.5;">
This document constitutes neither an offer to sell nor a solicitation of an offer to acquire securities. It is not a securities prospectus. All information relates to a planned venture and may be subject to change. Financial figures are projections and do not constitute a guarantee of future results. An investment in an early-stage venture involves significant risks, including the risk of total loss. German law applies. Place of jurisdiction is Konstanz, Germany.
</p>
</td>
</tr>
</table>
</td>
</tr>
+89
View File
@@ -0,0 +1,89 @@
import { FMResult } from '../types'
export interface AnnualKPI {
year: number
mrr: number
arr: number
customers: number
arpu: number
employees: number
revenuePerEmployee: number
personnelCosts: number
totalRevenue: number
totalCosts: number
ebit: number
ebitMargin: number
taxes: number
netIncome: number
serverCostPerCustomer: number
grossMargin: number
burnRate: number
runway: number | null
cashBalance: number
}
const TAX_RATE = 0.30 // ~30% Körperschaftsteuer + Gewerbesteuer + Soli
/**
* Aggregates 60 monthly FMResult entries into 5 annual KPI rows (20262030).
* All values are derived nothing is hardcoded.
*/
export function computeAnnualKPIs(results: FMResult[]): AnnualKPI[] {
if (!results || results.length === 0) return []
const years = [2026, 2027, 2028, 2029, 2030]
return years.map(year => {
const yearResults = results.filter(r => r.year === year)
if (yearResults.length === 0) {
return emptyKPI(year)
}
const dec = yearResults[yearResults.length - 1] // December snapshot
const totalRevenue = yearResults.reduce((s, r) => s + r.revenue_eur, 0)
const personnelCosts = yearResults.reduce((s, r) => s + r.personnel_eur, 0)
const totalCogs = yearResults.reduce((s, r) => s + r.cogs_eur, 0)
const totalInfra = yearResults.reduce((s, r) => s + r.infra_eur, 0)
const totalMarketing = yearResults.reduce((s, r) => s + r.marketing_eur, 0)
const totalCosts = yearResults.reduce((s, r) => s + r.total_costs_eur, 0)
const ebit = totalRevenue - totalCosts
const ebitMargin = totalRevenue > 0 ? (ebit / totalRevenue) * 100 : 0
const taxes = ebit > 0 ? Math.round(ebit * TAX_RATE) : 0
const netIncome = ebit - taxes
const serverCost = dec.total_customers > 0
? Math.round((totalInfra / 12) / dec.total_customers)
: 0
return {
year,
mrr: Math.round(dec.mrr_eur),
arr: Math.round(dec.arr_eur),
customers: Math.round(dec.total_customers),
arpu: dec.total_customers > 0 ? Math.round(dec.mrr_eur / dec.total_customers) : 0,
employees: Math.round(dec.employees_count),
revenuePerEmployee: dec.employees_count > 0 ? Math.round(totalRevenue / dec.employees_count) : 0,
personnelCosts: Math.round(personnelCosts),
totalRevenue: Math.round(totalRevenue),
totalCosts: Math.round(totalCosts),
ebit: Math.round(ebit),
ebitMargin: Math.round(ebitMargin),
taxes,
netIncome: Math.round(netIncome),
serverCostPerCustomer: serverCost,
grossMargin: Math.round(dec.gross_margin_pct),
burnRate: Math.round(dec.burn_rate_eur),
runway: dec.runway_months > 999 ? null : Math.round(dec.runway_months),
cashBalance: Math.round(dec.cash_balance_eur),
}
})
}
function emptyKPI(year: number): AnnualKPI {
return {
year, mrr: 0, arr: 0, customers: 0, arpu: 0, employees: 0,
revenuePerEmployee: 0, personnelCosts: 0, totalRevenue: 0, totalCosts: 0,
ebit: 0, ebitMargin: 0, taxes: 0, netIncome: 0,
serverCostPerCustomer: 0, grossMargin: 0, burnRate: 0, runway: null, cashBalance: 0,
}
}
+161 -18
View File
@@ -162,19 +162,26 @@ export async function computeFinanzplan(pool: Pool, scenarioId: string): Promise
const revenueRows = (umsatzRows.rows as FPUmsatzerloese[]).filter(r => r.section === 'revenue')
const totalRevenue = emptyMonthly()
// Revenue = quantity × price for each module
// Revenue = quantity × price for each module (if qty+price exist)
// Match by tier name extracted from parentheses, or exact label match
const extractTier = (label: string) => { const m = label.match(/\(([^)]+)\)/); return m ? m[1] : label }
// Revenue rows WITHOUT matching qty/price are kept as-is (e.g. Beratung & Service)
for (const rev of revenueRows) {
if (rev.row_label === 'GESAMTUMSATZ') continue
const qty = quantities.find(q => q.row_label === rev.row_label)
const price = prices.find(p => p.row_label === rev.row_label)
const tier = extractTier(rev.row_label)
const qty = quantities.find(q => extractTier(q.row_label) === tier) || quantities.find(q => q.row_label === rev.row_label)
const price = prices.find(p => extractTier(p.row_label) === tier) || prices.find(p => p.row_label === rev.row_label)
if (qty && price) {
for (let m = 1; m <= MONTHS; m++) {
const v = (qty.values[`m${m}`] || 0) * (price.values[`m${m}`] || 0)
rev.values[`m${m}`] = Math.round(v)
totalRevenue[`m${m}`] += rev.values[`m${m}`]
}
await pool.query('UPDATE fp_umsatzerloese SET values = $1 WHERE id = $2', [JSON.stringify(rev.values), rev.id])
}
// Add ALL revenue rows to total (computed or manual)
for (let m = 1; m <= MONTHS; m++) {
totalRevenue[`m${m}`] += rev.values[`m${m}`] || 0
}
}
// Update GESAMTUMSATZ
const gesamtUmsatz = revenueRows.find(r => r.row_label === 'GESAMTUMSATZ')
@@ -195,18 +202,115 @@ export async function computeFinanzplan(pool: Pool, scenarioId: string): Promise
for (let m = 1; m <= MONTHS; m++) {
const v = (qty.values[`m${m}`] || 0) * (uc.values[`m${m}`] || 0)
cost.values[`m${m}`] = Math.round(v)
totalMaterial[`m${m}`] += cost.values[`m${m}`]
}
await pool.query('UPDATE fp_materialaufwand SET values = $1 WHERE id = $2', [JSON.stringify(cost.values), cost.id])
}
// Add ALL cost rows to total (computed or manual)
for (let m = 1; m <= MONTHS; m++) {
totalMaterial[`m${m}`] += cost.values[`m${m}`] || 0
}
}
const matSumme = matCosts.find(r => r.row_label === 'SUMME')
if (matSumme) {
await pool.query('UPDATE fp_materialaufwand SET values = $1 WHERE id = $2', [JSON.stringify(totalMaterial), matSumme.id])
}
// 6. Betriebliche Aufwendungen — compute sum rows
// 5b. Headcount without founders (for formula-based costs)
const NUM_FOUNDERS = 2
const hcWithoutFounders = emptyMonthly()
for (let m = 1; m <= MONTHS; m++) {
hcWithoutFounders[`m${m}`] = Math.max(0, headcount[`m${m}`] - NUM_FOUNDERS)
}
// 5c. Total Bestandskunden (for Bewirtungskosten — uses totalBestandskunden from Serverkosten above)
// Also load enterprise customers separately for legacy compatibility
const kundenRows = await pool.query(
"SELECT segment_name, row_label, values FROM fp_kunden WHERE scenario_id = $1 AND row_label LIKE 'Bestandskunden%' ORDER BY sort_order",
[scenarioId]
)
const enterpriseKunden = emptyMonthly()
for (const row of kundenRows.rows) {
if (row.segment_name?.toLowerCase().includes('enterprise')) {
for (let m = 1; m <= MONTHS; m++) {
enterpriseKunden[`m${m}`] = row.values?.[`m${m}`] || 0
}
}
}
// 6. Betriebliche Aufwendungen — compute formula-based rows + sum rows
const betrieb = betriebRows.rows as FPBetrieblicheAufwendungen[]
// Pre-compute total Bestandskunden (needed for Bewirtungskosten + Serverkosten)
const totalBestandskunden = emptyMonthly()
for (const row of kundenRows.rows) {
const rl = (row as { row_label?: string }).row_label || ''
if (rl.includes('Bestandskunden') && !rl.includes('gesamt')) {
for (let m = 1; m <= MONTHS; m++) {
totalBestandskunden[`m${m}`] += row.values?.[`m${m}`] || 0
}
}
}
// Formula-based rows: derive from headcount (excl. founders) or customers
const formulaRows: { label: string; perUnit: number; source: MonthlyValues }[] = [
{ label: 'Fort-/Weiterbildungskosten (F)', perUnit: 300, source: hcWithoutFounders },
{ label: 'Fahrzeugkosten (F)', perUnit: 200, source: hcWithoutFounders },
{ label: 'KFZ-Steuern (F)', perUnit: 25, source: hcWithoutFounders },
{ label: 'KFZ-Versicherung (F)', perUnit: 150, source: hcWithoutFounders },
{ label: 'Reisekosten (F)', perUnit: 75, source: headcount },
{ label: 'Bewirtungskosten (F)', perUnit: 50, source: totalBestandskunden },
{ label: 'Internet/Mobilfunk (F)', perUnit: 50, source: headcount },
]
for (const fr of formulaRows) {
const row = betrieb.find(r => r.row_label === fr.label)
if (row) {
const computed = emptyMonthly()
for (let m = FOUNDING_MONTH; m <= MONTHS; m++) {
computed[`m${m}`] = Math.round((fr.source[`m${m}`] || 0) * fr.perUnit)
}
await pool.query('UPDATE fp_betriebliche_aufwendungen SET values = $1 WHERE id = $2', [JSON.stringify(computed), row.id])
row.values = computed
}
}
// Berufsgenossenschaft (VBG IT/Büro): ~0.5% of total brutto payroll
const bgRow = betrieb.find(r => r.row_label.includes('Berufsgenossenschaft'))
if (bgRow) {
const computed = emptyMonthly()
for (let m = FOUNDING_MONTH; m <= MONTHS; m++) {
computed[`m${m}`] = Math.round((totalBrutto[`m${m}`] || 0) * 0.005)
}
await pool.query('UPDATE fp_betriebliche_aufwendungen SET values = $1 WHERE id = $2', [JSON.stringify(computed), bgRow.id])
bgRow.values = computed
}
// Allgemeine Marketingkosten: 8% of revenue (2026-2028), 10% from 2029
const marketingRow = betrieb.find(r => r.row_label.includes('Allgemeine Marketingkosten'))
if (marketingRow) {
const computed = emptyMonthly()
for (let m = FOUNDING_MONTH; m <= MONTHS; m++) {
const rate = m <= 36 ? 0.08 : 0.10 // m36 = Dec 2028
computed[`m${m}`] = Math.round((totalRevenue[`m${m}`] || 0) * rate)
}
await pool.query('UPDATE fp_betriebliche_aufwendungen SET values = $1 WHERE id = $2', [JSON.stringify(computed), marketingRow.id])
marketingRow.values = computed
}
// Serverkosten now in Materialaufwand — compute Cloud-Hosting formula there
const matRows = materialRows.rows as FPMaterialaufwand[]
const cloudRow = matRows.find(r => r.row_label.includes('Cloud-Hosting'))
if (cloudRow) {
const computed = emptyMonthly()
for (let m = FOUNDING_MONTH; m <= MONTHS; m++) {
const kunden = totalBestandskunden[`m${m}`] || 0
const extraKunden = Math.max(0, kunden - 10) // first 10 included in base
computed[`m${m}`] = Math.round(extraKunden * 100 + 1500)
}
await pool.query('UPDATE fp_materialaufwand SET values = $1 WHERE id = $2', [JSON.stringify(computed), cloudRow.id])
cloudRow.values = computed
}
// Update Personalkosten row
const persBetrieb = betrieb.find(r => r.row_label === 'Personalkosten')
if (persBetrieb) {
@@ -220,6 +324,29 @@ export async function computeFinanzplan(pool: Pool, scenarioId: string): Promise
abrBetrieb.values = totalAfa
}
// Gewerbesteuer (F): 12.25% of monthly profit (only when positive)
// Monthly profit = Revenue - Material - Personnel - AfA - other opex (excl. taxes)
const gewStRow = betrieb.find(r => r.row_label.includes('Gewerbesteuer'))
if (gewStRow) {
const nonTaxOpex = betrieb.filter(r =>
r.category !== 'steuern' && r.category !== 'personal' && r.category !== 'abschreibungen' &&
!r.is_sum_row && !r.row_label.includes('Summe') && !r.row_label.includes('SUMME')
)
const computed = emptyMonthly()
for (let m = FOUNDING_MONTH; m <= MONTHS; m++) {
const rev = totalRevenue[`m${m}`] || 0
const mat = totalMaterial[`m${m}`] || 0
const pers = totalPersonal[`m${m}`] || 0
const afa = totalAfa[`m${m}`] || 0
let opex = 0
for (const r of nonTaxOpex) { opex += r.values[`m${m}`] || 0 }
const profit = rev - mat - pers - afa - opex
computed[`m${m}`] = profit > 0 ? Math.round(profit * 0.1225) : 0
}
await pool.query('UPDATE fp_betriebliche_aufwendungen SET values = $1 WHERE id = $2', [JSON.stringify(computed), gewStRow.id])
gewStRow.values = computed
}
// Compute category sums
const categories = ['steuern', 'versicherungen', 'besondere', 'marketing', 'sonstige']
for (const cat of categories) {
@@ -246,7 +373,7 @@ export async function computeFinanzplan(pool: Pool, scenarioId: string): Promise
}
// Gesamtkosten
const gesamtBetrieb = betrieb.find(r => r.row_label.includes('Gesamtkosten'))
const gesamtBetrieb = betrieb.find(r => r.row_label.includes('Gesamtkosten') || r.row_label.includes('SUMME Betriebliche'))
const totalSonstige = sonstSumme?.values || emptyMonthly()
if (gesamtBetrieb) {
const g = emptyMonthly()
@@ -298,13 +425,23 @@ export async function computeFinanzplan(pool: Pool, scenarioId: string): Promise
const kontostand = findLiq('Kontostand zu Beginn des Monats')
const liquiditaet = findLiq('LIQUIDITÄT')
// Operative Einzahlungen (OHNE Eigenkapital und Fremdkapital)
// Dynamically categorize rows by row_type instead of hardcoded labels
// Operative Einzahlungen (OHNE Eigenkapital, Fremdkapital, Stammkapital, Wandeldarlehen)
const einzahlungenOperativ = ['Umsatzerlöse', 'Sonst. betriebl. Erträge', 'Anzahlungen']
// Finanzierung (separat)
const finanzierung = ['Neuer Eigenkapitalzugang', 'Erhaltenes Fremdkapital']
// Operative Auszahlungen (OHNE Kreditrückzahlungen)
// Finanzierung: match any row with these keywords (handles renamed labels)
const finanzierungRows = liquid.filter(r =>
r.row_type === 'einzahlung' &&
!einzahlungenOperativ.includes(r.row_label) &&
!r.row_label.includes('Summe')
)
// Operative Auszahlungen
const auszahlungenOperativ = ['Materialaufwand', 'Personalkosten', 'Sonstige Kosten', 'Umsatzsteuer', 'Gewerbesteuer', 'Körperschaftsteuer']
const finanzAuszahlungen = ['Kreditrückzahlungen']
// Finanz-Auszahlungen: any auszahlung not in operativ list
const finanzAuszahlungRows = liquid.filter(r =>
r.row_type === 'auszahlung' &&
!auszahlungenOperativ.includes(r.row_label) &&
!r.row_label.includes('Summe')
)
// Summe EINZAHLUNGEN = nur operativ (für die Zeile "Summe Einzahlungen")
if (sumEin) {
@@ -358,13 +495,11 @@ export async function computeFinanzplan(pool: Pool, scenarioId: string): Promise
if (kontostand && liquiditaet && ueberschuss) {
// Berechne monatliche Finanzierungs-Cashflows
const finCF = emptyMonthly()
for (const label of finanzierung) {
const row = findLiq(label)
if (row) for (let m = 1; m <= MONTHS; m++) finCF[`m${m}`] += Math.round(row.values[`m${m}`] || 0)
for (const row of finanzierungRows) {
for (let m = 1; m <= MONTHS; m++) finCF[`m${m}`] += Math.round(row.values[`m${m}`] || 0)
}
for (const label of finanzAuszahlungen) {
const row = findLiq(label)
if (row) for (let m = 1; m <= MONTHS; m++) finCF[`m${m}`] -= Math.round(row.values[`m${m}`] || 0)
for (const row of finanzAuszahlungRows) {
for (let m = 1; m <= MONTHS; m++) finCF[`m${m}`] -= Math.round(row.values[`m${m}`] || 0)
}
const ks = emptyMonthly()
@@ -391,10 +526,18 @@ export async function computeFinanzplan(pool: Pool, scenarioId: string): Promise
const sonstigeAnnual = annualSums(totalSonstige)
// Write GuV rows
// Rohergebnis = Gesamtleistung - Materialaufwand
const rohergebnis: AnnualValues = {}
for (let y = 2026; y <= 2030; y++) {
const k = `y${y}`
rohergebnis[k] = Math.round((umsatzAnnual[k] || 0) - (materialAnnual[k] || 0))
}
const guvUpdates: { label: string; values: AnnualValues }[] = [
{ label: 'Umsatzerlöse', values: umsatzAnnual },
{ label: 'Gesamtleistung', values: umsatzAnnual },
{ label: 'Summe Materialaufwand', values: materialAnnual },
{ label: 'Rohergebnis', values: rohergebnis },
{ label: 'Löhne und Gehälter', values: personalBruttoAnnual },
{ label: 'Soziale Abgaben', values: personalSozialAnnual },
{ label: 'Summe Personalaufwand', values: personalAnnual },
+2 -3
View File
@@ -199,12 +199,11 @@ export interface SheetMeta {
export const SHEET_LIST: SheetMeta[] = [
{ name: 'kunden', label_de: 'Kunden', label_en: 'Customers', rows: 0, editable_rows: 0 },
{ name: 'umsatzerloese', label_de: 'Umsatzerloese', label_en: 'Revenue', rows: 0, editable_rows: 0 },
{ name: 'umsatzerloese', label_de: 'Umsatzerlöse', label_en: 'Revenue', rows: 0, editable_rows: 0 },
{ name: 'materialaufwand', label_de: 'Materialaufwand', label_en: 'Material Costs', rows: 0, editable_rows: 0 },
{ name: 'personalkosten', label_de: 'Personalkosten', label_en: 'Personnel', rows: 0, editable_rows: 0 },
{ name: 'betriebliche', label_de: 'Betriebliche Aufwendungen', label_en: 'Operating Expenses', rows: 0, editable_rows: 0 },
{ name: 'investitionen', label_de: 'Investitionen', label_en: 'Investments', rows: 0, editable_rows: 0 },
{ name: 'sonst_ertraege', label_de: 'Sonst. Ertraege', label_en: 'Other Income', rows: 0, editable_rows: 0 },
{ name: 'liquiditaet', label_de: 'Liquiditaet', label_en: 'Cash Flow', rows: 0, editable_rows: 0 },
{ name: 'liquiditaet', label_de: 'Liquidität', label_en: 'Cash Flow', rows: 0, editable_rows: 0 },
{ name: 'guv', label_de: 'GuV', label_en: 'P&L', rows: 0, editable_rows: 0 },
]
+4 -2
View File
@@ -3,7 +3,7 @@
import { useState, useEffect, useCallback, useRef } from 'react'
import { FMScenario, FMResult, FMComputeResponse, InvestorSnapshot } from '../types'
export function useFinancialModel(investorId?: string | null) {
export function useFinancialModel(investorId?: string | null, preferredScenarioId?: string | null) {
const [scenarios, setScenarios] = useState<FMScenario[]>([])
const [activeScenarioId, setActiveScenarioId] = useState<string | null>(null)
const [compareMode, setCompareMode] = useState(false)
@@ -51,7 +51,9 @@ export function useFinancialModel(investorId?: string | null) {
}
setScenarios(data)
const defaultScenario = data.find(s => s.is_default) || data[0]
// Use preferred scenario if available, otherwise default
const preferred = preferredScenarioId ? data.find(s => s.id === preferredScenarioId) : null
const defaultScenario = preferred || data.find(s => s.is_default) || data[0]
if (defaultScenario) {
setActiveScenarioId(defaultScenario.id)
}
+175
View File
@@ -0,0 +1,175 @@
'use client'
import { useState, useEffect } from 'react'
export interface FpAnnualKPIs {
revenue: number
ebit: number
personal: number
netIncome: number
steuern: number
liquiditaet: number
customers: number
headcount: number
mrr: number
arr: number
arpu: number
revPerEmp: number
ebitMargin: number
grossMargin: number
nrr: number // Net Revenue Retention %
paybackMonths: number // CAC Payback Period in months
}
interface SheetRow {
row_label?: string
values?: Record<string, number>
values_total?: Record<string, number>
}
/**
* Loads annual KPIs directly from fp_* tables (source of truth).
* Returns a map of year keys (y2026-y2030) to KPI objects.
*/
export function useFpKPIs(isWandeldarlehen?: boolean) {
const [kpis, setKpis] = useState<Record<string, FpAnnualKPIs>>({})
const [loading, setLoading] = useState(true)
useEffect(() => {
async function load() {
try {
const param = isWandeldarlehen ? '?scenarioId=c0000000-0000-0000-0000-000000000200' : ''
const [guvRes, liqRes, persRes, kundenRes] = await Promise.all([
fetch(`/api/finanzplan/guv${param}`, { cache: 'no-store' }),
fetch(`/api/finanzplan/liquiditaet${param}`, { cache: 'no-store' }),
fetch(`/api/finanzplan/personalkosten${param}`, { cache: 'no-store' }),
fetch(`/api/finanzplan/kunden${param}`, { cache: 'no-store' }),
])
const [guv, liq, pers, kunden] = await Promise.all([guvRes.json(), liqRes.json(), persRes.json(), kundenRes.json()])
const guvRows: SheetRow[] = guv.rows || []
const liqRows: SheetRow[] = liq.rows || []
const persRows: SheetRow[] = pers.rows || []
const kundenRows: SheetRow[] = kunden.rows || []
const findGuv = (label: string) => guvRows.find(r => (r.row_label || '').includes(label))
const findLiq = (label: string) => liqRows.find(r => (r.row_label || '').includes(label))
const kundenGesamt = kundenRows.find(r => r.row_label === 'Bestandskunden gesamt')
const result: Record<string, FpAnnualKPIs> = {}
for (const y of [2026, 2027, 2028, 2029, 2030]) {
const yk = `y${y}`
const mk = `m${(y - 2026) * 12 + 12}` // December
const revenue = findGuv('Umsatzerlöse')?.values?.[yk] || 0
const ebit = findGuv('EBIT')?.values?.[yk] || 0
const personal = findGuv('Summe Personalaufwand')?.values?.[yk] || 0
const netIncome = findGuv('Jahresüberschuss')?.values?.[yk] || findGuv('Jahresueber')?.values?.[yk] || 0
const steuern = findGuv('Steuern gesamt')?.values?.[yk] || 0
const liquiditaet = findLiq('LIQUIDIT')?.values?.[mk] || findLiq('LIQUIDITAET')?.values?.[mk] || 0
const customers = kundenGesamt?.values?.[mk] || 0
const headcount = persRows.filter(r => ((r.values_total || r.values)?.[mk] || 0) > 0).length
const mrr = revenue > 0 ? Math.round(revenue / 12) : 0
const arr = mrr * 12
const arpu = customers > 0 ? Math.round(mrr / customers) : 0
const revPerEmp = headcount > 0 ? Math.round(revenue / headcount) : 0
const ebitMargin = revenue > 0 ? Math.round((ebit / revenue) * 100) : 0
const grossMargin = revenue > 0 ? Math.round(((revenue - (findGuv('Summe Materialaufwand')?.values?.[yk] || 0)) / revenue) * 100) : 0
// NRR: compare Dec revenue of this year vs Dec revenue of prior year
// NRR = (MRR_Dec_thisYear / MRR_Dec_lastYear) * 100
const prevMk = `m${(y - 2026) * 12}` // December of prior year (m0 for 2026 = no prior)
const prevRevenue = y > 2026 ? (findGuv('Umsatzerlöse')?.values?.[`y${y - 1}`] || 0) : 0
const nrr = prevRevenue > 0 ? Math.round((revenue / prevRevenue) * 100) : 0
// Payback: Marketing costs / monthly gross profit per new customer
// Simplified: annual marketing spend / (new customers * monthly ARPU)
const sonstAufw = findGuv('Sonst. betriebl. Aufwend')?.values?.[yk] || 0
// Marketing ~ 10% of sonstige (rough estimate from our formula)
const marketingSpend = Math.round(revenue * 0.10)
const prevCustomers = y > 2026 ? (kundenGesamt?.values?.[`m${(y - 2026) * 12}`] || 0) : 0
const newCustomers = Math.max(customers - prevCustomers, 1)
const cac = newCustomers > 0 ? Math.round(marketingSpend / newCustomers) : 0
const monthlyGrossProfit = arpu > 0 ? arpu * (grossMargin / 100) : 0
const paybackMonths = monthlyGrossProfit > 0 ? Math.round(cac / monthlyGrossProfit) : 0
result[yk] = { revenue, ebit, personal, netIncome, steuern, liquiditaet, customers, headcount, mrr, arr, arpu, revPerEmp, ebitMargin, grossMargin, nrr, paybackMonths }
}
setKpis(result)
} catch { /* ignore */ }
setLoading(false)
}
load()
}, [isWandeldarlehen])
// Use of Funds: compute spending breakdown m8-m24 (funding period)
const [useOfFunds, setUseOfFunds] = useState<Array<{ category: string; label_de: string; label_en: string; percentage: number }>>([])
useEffect(() => {
async function loadUoF() {
try {
const param = isWandeldarlehen ? '?scenarioId=c0000000-0000-0000-0000-000000000200' : ''
const [persRes, betriebRes, investRes] = await Promise.all([
fetch(`/api/finanzplan/personalkosten${param}`, { cache: 'no-store' }),
fetch(`/api/finanzplan/betriebliche${param}`, { cache: 'no-store' }),
fetch(`/api/finanzplan/investitionen${param}`, { cache: 'no-store' }),
])
const [pers, betrieb, invest] = await Promise.all([persRes.json(), betriebRes.json(), investRes.json()])
// Sum spending m8-m24
const sumRange = (rows: SheetRow[], field: string, m1: number, m2: number) => {
let total = 0
for (const row of (rows || [])) {
const vals = (row as Record<string, unknown>)[field] as Record<string, number> || row.values || {}
for (let m = m1; m <= m2; m++) total += vals[`m${m}`] || 0
}
return total
}
const personalTotal = sumRange(pers.rows || [], 'values_total', 8, 24)
// Marketing rows from betriebliche
const betriebRows: SheetRow[] = betrieb.rows || []
const marketingRows = betriebRows.filter((r: SheetRow) =>
(r as Record<string, unknown>).category === 'marketing' && !(r as Record<string, unknown>).is_sum_row
)
const marketingTotal = sumRange(marketingRows, 'values', 8, 24)
// All other betriebliche (excl. personal, marketing, abschreibungen, sum rows)
const otherBetrieb = betriebRows.filter((r: SheetRow) => {
const any = r as Record<string, unknown>
return any.category !== 'marketing' && any.category !== 'personal' && any.category !== 'abschreibungen' &&
!any.is_sum_row && !(r.row_label || '').includes('Summe') && !(r.row_label || '').includes('SUMME')
})
const operationsTotal = sumRange(otherBetrieb, 'values', 8, 24)
// Investitionen
const investTotal = sumRange(invest.rows || [], 'values_invest', 8, 24)
const grandTotal = personalTotal + marketingTotal + operationsTotal + investTotal
if (grandTotal <= 0) return
const pctPersonal = Math.round((personalTotal / grandTotal) * 100)
const pctMarketing = Math.round((marketingTotal / grandTotal) * 100)
const pctOps = Math.round((operationsTotal / grandTotal) * 100)
const pctInvest = Math.round((investTotal / grandTotal) * 100)
// Adjust rounding to 100%
const pctReserve = 100 - pctPersonal - pctMarketing - pctOps - pctInvest
setUseOfFunds([
{ category: 'engineering', label_de: 'Engineering & Personal', label_en: 'Engineering & Personnel', percentage: pctPersonal },
{ category: 'sales', label_de: 'Vertrieb & Marketing', label_en: 'Sales & Marketing', percentage: pctMarketing },
{ category: 'operations', label_de: 'Betrieb & Infrastruktur', label_en: 'Operations & Infrastructure', percentage: pctOps },
{ category: 'hardware', label_de: 'Hardware & Ausstattung', label_en: 'Hardware & Equipment', percentage: pctInvest },
...(pctReserve > 0 ? [{ category: 'reserve', label_de: 'Reserve', label_en: 'Reserve', percentage: pctReserve }] : []),
].filter(f => f.percentage > 0))
} catch { /* ignore */ }
}
loadUoF()
}, [isWandeldarlehen])
const last = kpis.y2030
return { kpis, loading, last, useOfFunds }
}
+66 -66
View File
@@ -13,29 +13,29 @@ const translations = {
'Cover',
'Das Problem',
'Die Lösung',
'USP',
'Regulatorische Landschaft',
'Modularer Baukasten',
'So funktioniert\'s',
'Markt',
'Geschäftsmodell',
'Traction',
'Pricing',
'Meilensteine',
'Wettbewerb',
'Team',
'Finanzen',
'The Ask',
'Investition & Cap Table',
'Kundenersparnis',
'KI Q&A',
'Anhang: Strategie',
'Anhang: Finanzplan',
'Anhang: Annahmen',
'Anhang: Architektur',
'Anhang: Go-to-Market',
'Anhang: Regulatorik',
'Anhang: Systemarchitektur',
'Anhang: Engineering',
'Anhang: KI-Pipeline',
'Anhang: SDK Demo',
'Anhang: Strategie',
'Anhang: Finanzplan',
'Risiken & Mitigation',
'Glossar',
'Rechtlicher Hinweis',
],
executiveSummary: {
title: 'Executive Summary',
@@ -43,12 +43,12 @@ const translations = {
problem: 'Das Problem',
problemText: 'Unternehmen insbesondere im Maschinenbau stehen vor einem strategischen Dilemma: Um wettbewerbsfähig zu bleiben, müssen sie KI einsetzen. Gleichzeitig können oder wollen sie keine US-basierten KI-Anbieter in ihre sensibelsten Systeme integrieren. Wer auf US-SaaS verzichtet, verliert den Anschluss an die KI-Transformation. Wer sie nutzt, riskiert den Abfluss kritischer Daten und regulatorische Unsicherheit. Parallel dazu werden über 30.000 Unternehmen in Deutschland durch neue EU-Regulierungen wie AI Act, Data Act, CRA und NIS2 massiv belastet unabhängig von ihrer Größe oder digitalen Reife. Das Ergebnis: Entscheidungsblockade statt Innovation.',
solution: 'Unsere Lösung',
solutionText: 'Breakpilot ersetzt punktuelle Audits durch kontinuierliche, automatisierte Compliance und Security. Bei jeder Code-Änderung werden SAST, DAST, SBOM und Pentests automatisch ausgeführt. VVT, TOMs, DSFA, Löschfristen und CE-Risikobeurteilungen werden fortlaufend generiert. Audit-Abweichungen End-to-End: Rollen, Fristen, Tickets, Nachweise, Eskalation bis zur GF. Nahtlose Integration in bestehende Workflows (z.\u00a0B. Jira). BSI-Cloud DE oder OVH FR. Ergebnis: kontinuierliche Compliance statt punktueller Prüfungen.',
solutionText: 'Breakpilot ersetzt punktuelle Audits durch kontinuierliche, automatisierte Compliance und Security. Bei jeder Code-Änderung werden SAST, DAST, SBOM und Pentests automatisch ausgeführt. VVT, TOMs, DSFA, Löschfristen und CE-Risikobeurteilungen werden fortlaufend generiert. Audit-Abweichungen End-to-End: Rollen, Fristen, Tickets, Nachweise, Eskalation bis zur GF. Nahtlose Integration in bestehende Workflows über den Issue-Tracker deiner Wahl. BSI-Cloud DE. Ergebnis: kontinuierliche Compliance statt punktueller Prüfungen.',
roi: 'Kundenersparnis',
roiText: 'Kunden zahlen ca. 50.000 EUR/Jahr und sparen: 30.000 EUR Pentests, 20.000 EUR CE-Beurteilungen, Auditmanager-Kosten und Strafrisiko. ROI ab Tag 1.',
market: 'Markt',
businessModel: 'Geschäftsmodell',
businessModelText: 'Kunden zahlen ~50.000 EUR/Jahr und sparen 50.000+ EUR (Pentests, CE-Beurteilungen, Auditmanager). ROI ab Tag 1. BSI-Cloud DE oder OVH FR.',
businessModelText: 'Kunden zahlen ~50.000 EUR/Jahr und sparen 50.000+ EUR (Pentests, CE-Beurteilungen, Auditmanager). ROI ab Tag 1. BSI-Cloud DE.',
keyMetrics: 'Kennzahlen',
documents: 'Originaldokumente',
controls: 'Prüfaspekte',
@@ -63,31 +63,31 @@ const translations = {
uspText: 'Einzige Plattform mit kontinuierlicher Code-Security, automatischer Compliance-Dokumentation und CE-Software-Risikobeurteilung — auf deutscher oder französischer Cloud.',
},
cover: {
tagline: 'Compliance & Code-Security für den Maschinenbau',
tagline: 'Compliance & Code-Security',
subtitle: 'Pre-Seed · Q4 2026',
cta: 'Pitch starten',
},
problem: {
title: 'Das Problem',
subtitle: 'Maschinenbauer wollen KI nutzen — aber nicht um den Preis ihrer Datensouveränität',
subtitle: 'Deutsche und europäische Unternehmen wollen KI nutzen — aber nicht um den Preis ihrer Datensouveränität',
cards: [
{
title: 'KI-Dilemma',
stat: 'Abgehängt',
desc: 'Maschinenbauer wollen KI nutzen, aber keinen Microsoft Copilot oder Claude auf ihr Herzstück lassen. Angst vor Datenmissbrauch durch US-Konzerne ist real. Wer US-SaaS meidet, bleibt von der KI-Revolution abgeschnitten.',
desc: 'Produzierende Unternehmen brauchen KI, um wettbewerbsfähig zu bleiben. Aber Microsoft Copilot, ChatGPT oder Claude an den eigenen Quellcode und die Konstruktionsdaten zu lassen, kommt für die meisten nicht in Frage. Wer auf US-KI verzichtet, verliert den Anschluss. Wer sie nutzt, riskiert seine Datensouveränität.',
},
{
title: 'Patriots Act',
title: 'Patriot Act + FISA 702',
stat: 'Kein Schutz',
desc: 'Die Alternative: Alles zu AWS, Google oder Microsoft schieben. Aber selbst europäische Server der US-Player können über den Patriots Act abgesaugt werden. Deutsche KMU sitzen in der Falle.',
desc: 'Selbst wer EU-Server bei AWS, Google oder Microsoft bucht, ist nicht geschützt. US-Gesetze wie FISA 702 und der Cloud Act gelten extraterritorial — US-Behörden können auf Daten zugreifen, egal wo der Server steht. Das Schrems-II-Urteil des EuGH hat das bestätigt.',
},
{
title: 'Regulierungs-Tsunami',
stat: '50.000+ EUR/Jahr',
desc: 'AI Act, NIS2, CRA, DSGVO, Maschinenverordnung — 5+ Gesetze gleichzeitig. Pentests und CE-Zertifizierungen kosten 50.000+ EUR/Jahr, prüfen aber nur einmal. KMU mit 10-500 MA haben weder Personal noch Budget.',
stat: 'Nicht tragbar',
desc: 'Seit 2024 greifen AI Act, NIS2 und Cyber Resilience Act — zusätzlich zu DSGVO, Data Act, Maschinenverordnung und Lieferkettengesetz. Europäische Unternehmen tragen damit Compliance-Kosten, die US- und Asien-Konkurrenten nicht haben. Gleichzeitig steigen Kosten durch Rohstoffengpässe und geopolitische Krisen. KMU können das nicht mehr stemmen.',
},
],
quote: 'Maschinenbauer brauchen eine KI-Lösung, die in Deutschland läuft, ihren Code schützt und Compliance automatisiert — ohne ihre Daten an US-Konzerne zu geben.',
quote: 'Produzierende Unternehmen brauchen eine KI-Lösung, die in Europa läuft, ihren Code schützt und Compliance automatisiert — ohne ihre Daten an US-Konzerne zu geben.',
},
solution: {
title: 'Die Lösung',
@@ -95,7 +95,7 @@ const translations = {
pillars: [
{
title: 'Kontinuierliche Code-Security',
desc: 'SAST, DAST, SBOM und Pentesting bei jeder Code-Änderung — nicht einmal im Jahr. Findings direkt als Jira-Tickets mit Implementierungsvorschlägen. 30.000+ EUR/Jahr Pentest-Kosten gespart.',
desc: 'SAST, DAST, SBOM und Pentesting bei jeder Code-Änderung — nicht einmal im Jahr. Findings direkt als Tickets im Issue-Tracker deiner Wahl, mit Implementierungsvorschlägen. 15.000+ EUR pro Jahr und Anwendung an Pentest-Kosten gespart.',
icon: 'scan',
},
{
@@ -105,14 +105,14 @@ const translations = {
},
{
title: 'Deutsche Cloud, volle Integration',
desc: 'BSI-zertifizierte Cloud in DE oder OVH in FR. Jitsi (Video), Matrix (Chat), KI-Aufgabenerstellung aus Audio. Keine US-SaaS im Source Code. Optional Mac Mini für maximale Privacy.',
desc: 'BSI-zertifizierte Cloud in Deutschland. Live-Support über Jitsi (Video) und Matrix (Chat). Keine US-SaaS im Source Code. Optional Mac Mini/Studio für maximale Privacy.',
icon: 'server',
},
],
},
regulatoryLandscape: {
title: 'Regulatorische Landschaft',
subtitle: '110 Gesetze & Regularien, 10 Branchen — eine Plattform',
subtitle: '380+ Dokumente im RAG — 10 Industriesektoren — eine Plattform',
documents: 'Originaldokumente',
controls: 'Extrahierte Controls',
regulations: 'Regularien',
@@ -148,7 +148,7 @@ const translations = {
pricingTitle: 'Pricing nach Unternehmensgröße',
pricingSubtitle: 'Mitarbeiterbasiert — validiert am Markt',
cloud: 'Cloud-Lösung (Standard)',
cloudDesc: 'BSI-Cloud DE oder OVH FR. Für alle Unternehmensgrößen.',
cloudDesc: 'BSI-Cloud DE. Für alle Unternehmensgrößen.',
privacy: 'Privacy-Hardware (optional)',
privacyDesc: 'Mac Mini / Studio für Kleinstunternehmen (<10 MA) mit absolutem Privacy-Bedarf.',
},
@@ -158,7 +158,7 @@ const translations = {
steps: [
{
title: 'Cloud-Vertrag abschließen',
desc: 'BSI-zertifizierte Cloud in Deutschland oder OVH in Frankreich. Fixe oder flexible Kosten. Für Kleinstunternehmen optional: Mac Mini vorkonfiguriert.',
desc: 'BSI-zertifizierte Cloud in Deutschland. Fixe oder flexible Kosten.',
},
{
title: 'Code-Repos verbinden',
@@ -176,19 +176,19 @@ const translations = {
},
market: {
title: 'Marktchance',
subtitle: 'Der Maschinenbau braucht Compliance & Code-Security',
subtitle: 'Compliance & Code-Security für produzierende Unternehmen',
tam: 'TAM',
sam: 'SAM',
som: 'SOM',
tamLabel: 'Total Addressable Market',
samLabel: 'Serviceable Addressable Market',
somLabel: 'Serviceable Obtainable Market',
somLabel: 'Serviceable Obtainable Market (nur Maschinen- und Anlagenbauer als Kernmarkt betrachtet)',
source: 'Quelle',
growth: 'Wachstum p.a.',
},
businessModel: {
title: 'Geschäftsmodell',
subtitle: 'Modulares SaaS mit Savings-Argument',
title: 'Pricing',
subtitle: 'Mitarbeiterbasiertes SaaS — Kunden sparen mehr als sie zahlen',
unitEconomics: 'Unit Economics',
amortization: 'Amortisation',
margin: 'Marge',
@@ -204,8 +204,8 @@ const translations = {
savingsTotal: 'Ersparnis',
},
traction: {
title: 'Traction & Meilensteine',
subtitle: 'Unser bisheriger Fortschritt',
title: 'Meilensteine',
subtitle: 'Was wir bereits erreicht haben — und was als Nächstes kommt',
completed: 'Abgeschlossen',
inProgress: 'In Arbeit',
planned: 'Geplant',
@@ -263,10 +263,10 @@ const translations = {
send: 'Senden',
thinking: 'Denke nach...',
suggestions: [
'Wie funktioniert die Code-Security für Firmware?',
'Warum plant ihr eine optionale 2. Finanzierungsrunde?',
'Warum können Proliance und DataGuard das nicht?',
'Was kostet die Lösung für einen Maschinenbauer?',
'Wie sieht die Risikoanalyse für unsere Software aus?',
'Wie funktioniert die Code-Security für Firmware?',
],
},
annex: {
@@ -275,8 +275,8 @@ const translations = {
subtitle: 'Drei Szenarien für robuste Planung',
},
architecture: {
title: 'Technische Architektur',
subtitle: 'Self-Hosted KI-Stack für maximale Datensouveränität',
title: 'Systemarchitektur',
subtitle: 'BreakPilot · CERTifAI · Compliance Scanner — verbunden über LiteLLM',
},
gtm: {
title: 'Go-to-Market Strategie',
@@ -308,29 +308,29 @@ const translations = {
'Cover',
'The Problem',
'The Solution',
'USP',
'Regulatory Landscape',
'Modular Toolkit',
'How It Works',
'Market',
'Business Model',
'Traction',
'Pricing',
'Milestones',
'Competition',
'Team',
'Financials',
'The Ask',
'Investment & Cap Table',
'Customer Savings',
'AI Q&A',
'Appendix: Strategy',
'Appendix: Financial Plan',
'Appendix: Assumptions',
'Appendix: Architecture',
'Appendix: Go-to-Market',
'Appendix: Regulatory',
'Appendix: System Architecture',
'Appendix: Engineering',
'Appendix: AI Pipeline',
'Appendix: SDK Demo',
'Appendix: Strategy',
'Appendix: Financial Plan',
'Risks & Mitigation',
'Glossary',
'Legal Notice',
],
executiveSummary: {
title: 'Executive Summary',
@@ -338,12 +338,12 @@ const translations = {
problem: 'The Problem',
problemText: 'Many companies, especially in manufacturing, want to use AI — but refuse to let American AI providers access their core IP. Those avoiding US SaaS are cut off from the AI revolution. Those using these providers accept that data may be processed in the US. Meanwhile, new EU regulations (AI Act, Data Act, CRA, NIS2 etc.) affect over 30,000 companies in Germany alone — regardless of size.',
solution: 'Our Solution',
solutionText: 'Continuous code security instead of annual spot checks: SAST, DAST, SBOM, pentesting on every change. RoPA, TOMs, DPIA, retention policies, CE risk assessment automatically. Audit deviations end-to-end: roles, deadlines, tickets, evidence, escalation to management. Jira integration. Academy. BSI cloud DE or OVH FR.',
solutionText: 'Continuous code security instead of annual spot checks: SAST, DAST, SBOM, pentesting on every change. RoPA, TOMs, DPIA, retention policies, CE risk assessment automatically. Audit deviations end-to-end: roles, deadlines, tickets, evidence, escalation to management. Issue tracker of your choice. Academy. BSI cloud DE.',
roi: 'Customer Savings',
roiText: 'Customers pay ~EUR 50,000/year and save: EUR 30,000 pentests, EUR 20,000 CE assessments, audit manager costs and penalty risk. ROI from day 1.',
market: 'Market',
businessModel: 'Business Model',
businessModelText: 'Customers pay ~EUR 50,000/year and save EUR 50,000+ (pentests, CE assessments, audit managers). ROI from day 1. BSI cloud DE or OVH FR.',
businessModelText: 'Customers pay ~EUR 50,000/year and save EUR 50,000+ (pentests, CE assessments, audit managers). ROI from day 1. BSI cloud DE.',
keyMetrics: 'Key Metrics',
documents: 'Original Documents',
controls: 'Audit Aspects',
@@ -358,31 +358,31 @@ const translations = {
uspText: 'Only platform with continuous code security, automatic compliance documentation and CE software risk assessment — on German or French cloud.',
},
cover: {
tagline: 'Compliance & Code Security for Machine Manufacturers',
tagline: 'Compliance & Code Security',
subtitle: 'Pre-Seed · Q4 2026',
cta: 'Start Pitch',
},
problem: {
title: 'The Problem',
subtitle: 'Machine manufacturers want AI — but not at the cost of their data sovereignty',
subtitle: 'German and European companies want AI — but not at the cost of their data sovereignty',
cards: [
{
title: 'AI Dilemma',
stat: 'Left Behind',
desc: 'Machine manufacturers want to use AI but refuse Microsoft Copilot or Claude on their core IP. Fear of data misuse by US corporations is real. Those avoiding US SaaS are cut off from the AI revolution.',
desc: 'Manufacturing companies need AI to stay competitive. But letting Microsoft Copilot, ChatGPT or Claude access their source code and engineering data is out of the question for most. Those avoiding US AI fall behind. Those using it risk their data sovereignty.',
},
{
title: 'Patriot Act',
title: 'Patriot Act + FISA 702',
stat: 'No Protection',
desc: 'The alternative: push everything to AWS, Google or Microsoft. But even European servers of US players can be accessed via the Patriot Act. German SMEs are trapped.',
desc: 'Even booking EU servers at AWS, Google or Microsoft offers no protection. US laws like FISA 702 and the Cloud Act apply extraterritorially — US authorities can access data regardless of server location. The Schrems II ruling by the CJEU confirmed this.',
},
{
title: 'Regulation Tsunami',
stat: 'EUR 50,000+/yr',
desc: 'AI Act, NIS2, CRA, GDPR, Machinery Regulation — 5+ laws simultaneously. Pentests and CE certifications cost EUR 50,000+/year but only check once. SMEs with 10-500 employees lack staff and budget.',
stat: 'Unsustainable',
desc: 'Since 2024, the AI Act, NIS2 and Cyber Resilience Act apply — on top of GDPR, Data Act, Machinery Regulation and Supply Chain Act. European companies bear compliance costs that US and Asian competitors do not face. At the same time, costs rise from raw material shortages and geopolitical crises. SMEs can no longer handle this alone.',
},
],
quote: 'Machine manufacturers need an AI solution that runs in Germany, protects their code and automates compliance — without giving their data to US corporations.',
quote: 'Manufacturing companies need an AI solution that runs in Europe, protects their code and automates compliance — without giving their data to US corporations.',
},
solution: {
title: 'The Solution',
@@ -390,7 +390,7 @@ const translations = {
pillars: [
{
title: 'Continuous Code Security',
desc: 'SAST, DAST, SBOM and pentesting on every code change — not once a year. Findings as Jira tickets with implementation suggestions. EUR 30,000+/year pentest costs saved.',
desc: 'SAST, DAST, SBOM and pentesting on every code change — not once a year. Findings as tickets in the issue tracker of your choice, with implementation suggestions. EUR 15,000+ per year per application in pentest costs saved.',
icon: 'scan',
},
{
@@ -400,14 +400,14 @@ const translations = {
},
{
title: 'German Cloud, Full Integration',
desc: 'BSI-certified cloud in DE or OVH in FR. Jitsi (video), Matrix (chat), AI task creation from audio. No US SaaS in source code. Optional Mac Mini for maximum privacy.',
desc: 'BSI-certified cloud in Germany. Live support via Jitsi (video) and Matrix (chat). No US SaaS in source code. Optional Mac Mini/Studio for maximum privacy.',
icon: 'server',
},
],
},
regulatoryLandscape: {
title: 'Regulatory Landscape',
subtitle: '110 laws & regulations, 10 industries — one platform',
subtitle: '380+ documents in RAG — 10 industry sectors — one platform',
documents: 'Original Documents',
controls: 'Extracted Controls',
regulations: 'Regulations',
@@ -443,7 +443,7 @@ const translations = {
pricingTitle: 'Pricing by Company Size',
pricingSubtitle: 'Employee-based — market validated',
cloud: 'Cloud Solution (Standard)',
cloudDesc: 'BSI cloud DE or OVH FR. For all company sizes.',
cloudDesc: 'BSI cloud DE. For all company sizes.',
privacy: 'Privacy Hardware (optional)',
privacyDesc: 'Mac Mini / Studio for micro businesses (<10 employees) with absolute privacy needs.',
},
@@ -453,7 +453,7 @@ const translations = {
steps: [
{
title: 'Sign Cloud Contract',
desc: 'BSI-certified cloud in Germany or OVH in France. Fixed or flexible costs. For micro businesses optionally: pre-configured Mac Mini.',
desc: 'BSI-certified cloud in Germany. Fixed or flexible costs.',
},
{
title: 'Connect Code Repos',
@@ -471,19 +471,19 @@ const translations = {
},
market: {
title: 'Market Opportunity',
subtitle: 'Machine manufacturing needs compliance & code security',
subtitle: 'Compliance & code security for manufacturing companies',
tam: 'TAM',
sam: 'SAM',
som: 'SOM',
tamLabel: 'Total Addressable Market',
samLabel: 'Serviceable Addressable Market',
somLabel: 'Serviceable Obtainable Market',
somLabel: 'Serviceable Obtainable Market (machine & plant manufacturers as core market only)',
source: 'Source',
growth: 'Growth p.a.',
},
businessModel: {
title: 'Business Model',
subtitle: 'Modular SaaS with Savings Argument',
title: 'Pricing',
subtitle: 'Employee-based SaaS — customers save more than they pay',
unitEconomics: 'Unit Economics',
amortization: 'Amortization',
margin: 'Margin',
@@ -499,8 +499,8 @@ const translations = {
savingsTotal: 'Total Savings',
},
traction: {
title: 'Traction & Milestones',
subtitle: 'Our progress so far',
title: 'Milestones',
subtitle: 'What we have achieved — and what comes next',
completed: 'Completed',
inProgress: 'In Progress',
planned: 'Planned',
@@ -558,10 +558,10 @@ const translations = {
send: 'Send',
thinking: 'Thinking...',
suggestions: [
'How does code security work for firmware?',
'Why do you plan an optional 2nd funding round?',
'Why can\'t Proliance and DataGuard do this?',
'What does the solution cost for a machine manufacturer?',
'What does the risk assessment for our software look like?',
'How does code security work for firmware?',
],
},
annex: {
@@ -570,8 +570,8 @@ const translations = {
subtitle: 'Three scenarios for robust planning',
},
architecture: {
title: 'Technical Architecture',
subtitle: 'Self-hosted AI stack for maximum data sovereignty',
title: 'System Architecture',
subtitle: 'BreakPilot · CERTifAI · Compliance Scanner — connected via LiteLLM',
},
gtm: {
title: 'Go-to-Market Strategy',
+478 -49
View File
@@ -17,8 +17,8 @@ export const PRESENTER_FAQ: FAQEntry[] = [
keywords: ['module', 'modules', 'funktionen', 'features', 'umfang', 'scope', 'wieviele', 'how many'],
question_de: 'Welche Module hat die Plattform?',
question_en: 'What modules does the platform have?',
answer_de: '65+ Compliance-Module in zwei Saeulen: Unternehmens-Compliance — alle Datenkategorien, Verarbeitungen, Dienstleister und Auftragsverarbeiter erfassen, automatische Dokumentenerstellung (AGB, DSE, Cookie Banner, Nutzungsbedingungen), DSR-Prozess, Dokumentenversionierung, Rollenverwaltung, Academy und Schulungen, Audit und Nachweismanagement. Code/CE-Seite — Repo-Scanning (SAST + DAST), Pentesting, CE Software Risk Assessment (IACE), automatische SBOM-Generierung, Jira/Atlassian-Integration mit konkreten Code-Änderungsvorschlaegen.',
answer_en: 'modular compliance platform in two pillars: Company-side compliance — capture all data categories, processes, providers and processors, auto-generate legal documents (Terms of Service, Privacy Policy, Cookie Banner, Terms of Use), DSR process, document versioning, role management, academy and training, audit and evidence management. Code/CE side — repo scanning (SAST + DAST), pentesting, CE Software Risk Assessment (IACE), automatic SBOM generation, Jira/Atlassian integration with specific code change suggestions.',
answer_de: '12 Produkt-Module in zwei Saeulen: Unternehmens-Compliance — alle Datenkategorien, Verarbeitungen, Dienstleister und Auftragsverarbeiter erfassen, automatische Dokumentenerstellung (AGB, DSE, Cookie Banner, Nutzungsbedingungen), DSR-Prozess, Dokumentenversionierung, Rollenverwaltung, Academy und Schulungen, Audit und Nachweismanagement. Code/CE-Seite — Repo-Scanning (SAST + DAST), Pentesting, CE Software Risk Assessment (IACE), automatische SBOM-Generierung, Integration in den Issue-Tracker deiner Wahl mit konkreten Code-Änderungsvorschlaegen.',
answer_en: 'modular compliance platform in two pillars: Company-side compliance — capture all data categories, processes, providers and processors, auto-generate legal documents (Terms of Service, Privacy Policy, Cookie Banner, Terms of Use), DSR process, document versioning, role management, academy and training, audit and evidence management. Code/CE side — repo scanning (SAST + DAST), pentesting, CE Software Risk Assessment (IACE), automatic SBOM generation, integration with the issue tracker of your choice with specific code change suggestions.',
goto_slide: 'solution',
priority: 8,
},
@@ -37,8 +37,8 @@ export const PRESENTER_FAQ: FAQEntry[] = [
keywords: ['code', 'ce', 'scanning', 'sast', 'dast', 'sbom', 'iace', 'firmware', 'repo', 'repository', 'devsecops'],
question_de: 'Wie funktioniert die Code- und CE-Compliance?',
question_en: 'How does code and CE compliance work?',
answer_de: 'Die Code/CE-Seite umfasst vollständiges Repo-Scanning mit SAST und DAST, automatisiertes Pentesting sowie CE Software Risk Assessment (IACE) — auch für Elektronik-Produkte. Gefundene Schwachstellen werden direkt als Jira/Atlassian-Tickets mit konkreten Code-Änderungsvorschlaegen erstellt. Die KI implementiert Fixes automatisch. Zusaetzlich wird eine vollständige SBOM (Software Bill of Materials) generiert.',
answer_en: 'The code/CE side includes full repo scanning with SAST and DAST, automated pentesting, and CE Software Risk Assessment (IACE) — also for electronics products. Findings are directly created as Jira/Atlassian tickets with specific code change suggestions. The AI implements fixes automatically. Additionally, a complete SBOM (Software Bill of Materials) is generated.',
answer_de: 'Die Code/CE-Seite umfasst vollständiges Repo-Scanning mit SAST und DAST, automatisiertes Pentesting sowie CE Software Risk Assessment (IACE) — auch für Elektronik-Produkte. Gefundene Schwachstellen werden direkt als Tickets im Issue-Tracker deiner Wahl mit konkreten Code-Änderungsvorschlaegen erstellt. Die KI implementiert Fixes automatisch. Zusaetzlich wird eine vollständige SBOM (Software Bill of Materials) generiert.',
answer_en: 'The code/CE side includes full repo scanning with SAST and DAST, automated pentesting, and CE Software Risk Assessment (IACE) — also for electronics products. Findings are directly created as tickets in the issue tracker of your choice with specific code change suggestions. The AI implements fixes automatically. Additionally, a complete SBOM (Software Bill of Materials) is generated.',
goto_slide: 'how-it-works',
priority: 8,
},
@@ -58,8 +58,8 @@ export const PRESENTER_FAQ: FAQEntry[] = [
keywords: ['llm', 'modell', 'model', 'ki', 'ai', 'kuenstliche intelligenz', 'artificial intelligence', 'welches', 'which', '1000b'],
question_de: 'Welches KI-Modell nutzt ihr?',
question_en: 'Which AI model do you use?',
answer_de: 'Unser Haupt-LLM hat 1000 Milliarden Parameter und läuft ausschliesslich bei BSI-zertifizierten Hostern in Deutschland und Frankreich — SysEleven, OVH und Hetzner. Keine amerikanischen Anbieter, keine Daten verlassen die Server. Jeder Kunde bekommt einen isolierten Namespace. Für die Mac Mini/Studio Variante setzen wir kleinere Modelle ein, die lokale Dokumentenverarbeitung und RAG-Abfragen uebernehmen.',
answer_en: 'Our main LLM has 1,000 billion parameters and runs exclusively at BSI-certified hosters in Germany and France — SysEleven, OVH and Hetzner. No American providers, no data leaves the servers. Each customer gets an isolated namespace. For the Mac Mini/Studio variant, we use smaller models for local document processing and RAG queries.',
answer_de: 'Unser Haupt-LLM hat 1000 Milliarden Parameter und läuft ausschliesslich bei BSI-zertifizierten Hostern in Deutschland und Frankreich — SysEleven und Hetzner. Keine amerikanischen Anbieter, keine Daten verlassen die Server. Jeder Kunde bekommt einen isolierten Namespace. Für die Mac Mini/Studio Variante setzen wir kleinere Modelle ein, die lokale Dokumentenverarbeitung und RAG-Abfragen uebernehmen.',
answer_en: 'Our main LLM has 1,000 billion parameters and runs exclusively at BSI-certified hosters in Germany and France — SysEleven and Hetzner. No American providers, no data leaves the servers. Each customer gets an isolated namespace. For the Mac Mini/Studio variant, we use smaller models for local document processing and RAG queries.',
goto_slide: 'product',
priority: 8,
},
@@ -68,8 +68,8 @@ export const PRESENTER_FAQ: FAQEntry[] = [
keywords: ['hosting', 'europa', 'europe', 'eu', 'deutschland', 'germany', 'frankreich', 'france', 'bsi', 'syseleven', 'ovh', 'hetzner', 'souveraenitaet', 'sovereignty', 'american', 'us', 'usa'],
question_de: 'Wo werden die Daten gehostet?',
question_en: 'Where is the data hosted?',
answer_de: 'Ausschliesslich in Deutschland und Frankreich bei BSI-zertifizierten Anbietern: SysEleven, OVH und Hetzner. Keine amerikanischen Cloud-Provider, kein AWS, kein Azure, kein GCP. Die Daten verlassen niemals die europäischen Server. Jeder Kunde erhaelt einen vollständig isolierten Namespace — es gibt keine geteilte Datenverarbeitung zwischen Mandanten.',
answer_en: 'Exclusively in Germany and France at BSI-certified providers: SysEleven, OVH and Hetzner. No American cloud providers, no AWS, no Azure, no GCP. Data never leaves the European servers. Each customer receives a fully isolated namespace — there is no shared data processing between tenants.',
answer_de: 'Ausschliesslich in Deutschland und Frankreich bei BSI-zertifizierten Anbietern: SysEleven und Hetzner. Keine amerikanischen Cloud-Provider, kein AWS, kein Azure, kein GCP. Die Daten verlassen niemals die europäischen Server. Jeder Kunde erhaelt einen vollständig isolierten Namespace — es gibt keine geteilte Datenverarbeitung zwischen Mandanten.',
answer_en: 'Exclusively in Germany and France at BSI-certified providers: SysEleven and Hetzner. No American cloud providers, no AWS, no Azure, no GCP. Data never leaves the European servers. Each customer receives a fully isolated namespace — there is no shared data processing between tenants.',
goto_slide: 'annex-architecture',
priority: 9,
},
@@ -78,8 +78,8 @@ export const PRESENTER_FAQ: FAQEntry[] = [
keywords: ['rag', 'rechtstexte', 'legal texts', 'wissensbasis', 'knowledge base', 'vektordatenbank', 'vector', 'gesetze', 'laws'],
question_de: 'Wie funktioniert die Wissensbasis?',
question_en: 'How does the knowledge base work?',
answer_de: 'Unsere RAG-Engine (Retrieval Augmented Generation) umfasst 25.000+ indexierte Originaldokumente und über 25.000 extrahierte Controls — DSGVO, AI Act, CRA, NIS2, Maschinenverordnung und 110 Gesetze und Regularien für 10 Branchen. Bei jeder Compliance-Analyse werden die relevanten Paragraphen und Controls automatisch herangezogen. KI-Agenten arbeiten als spezialisierte Services und liefern rechtlich fundierte Antworten mit Quellennachweis.',
answer_en: 'Our RAG engine (Retrieval Augmented Generation) includes 25.000+ indexed original documents and over 25,000 extracted controls — GDPR, AI Act, CRA, NIS2, Machinery Regulation and 110 laws and regulations across 10 industries. For every compliance analysis, the relevant paragraphs and controls are automatically retrieved. AI agents operate as specialized services and deliver legally grounded answers with source references.',
answer_de: 'Unsere RAG-Engine (Retrieval Augmented Generation) umfasst über 25 Tausend indexierte Originaldokumente und über 25.000 extrahierte Controls — DSGVO, AI Act, CRA, NIS2, Maschinenverordnung und 380+ Gesetze und Regularien für 10 Branchen. Bei jeder Compliance-Analyse werden die relevanten Paragraphen und Controls automatisch herangezogen. KI-Agenten arbeiten als spezialisierte Services und liefern rechtlich fundierte Antworten mit Quellennachweis.',
answer_en: 'Our RAG engine (Retrieval Augmented Generation) includes over 25,000 indexed original documents and over 25,000 extracted controls — GDPR, AI Act, CRA, NIS2, Machinery Regulation and 380+ laws and regulations across 10 industries. For every compliance analysis, the relevant paragraphs and controls are automatically retrieved. AI agents operate as specialized services and deliver legally grounded answers with source references.',
goto_slide: 'product',
priority: 7,
},
@@ -103,12 +103,12 @@ export const PRESENTER_FAQ: FAQEntry[] = [
priority: 8,
},
{
id: 'tech-jira-integration',
keywords: ['jira', 'atlassian', 'integration', 'ticket', 'tickets', 'issue', 'issues', 'fix', 'fixes', 'code change'],
question_de: 'Wie funktioniert die Jira/Atlassian-Integration?',
question_en: 'How does the Jira/Atlassian integration work?',
answer_de: 'Wenn das Code-Scanning (SAST/DAST) oder Pentesting Schwachstellen findet, erstellt die Plattform automatisch Jira-Tickets mit exakten Code-Änderungsvorschlaegen welche Datei, welche Zeile, welcher Fix. Die KI kann den Fix auch direkt implementieren. So schliesst sich der Kreis von Finding zu Fix vollständig automatisiert.',
answer_en: 'When code scanning (SAST/DAST) or pentesting finds vulnerabilities, the platform automatically creates Jira tickets with exact code change suggestions which file, which line, which fix. The AI can also implement the fix directly. This closes the loop from finding to fix fully automated.',
id: 'tech-issue-tracker-integration',
keywords: ['jira', 'atlassian', 'linear', 'issue tracker', 'integration', 'ticket', 'tickets', 'issue', 'issues', 'fix', 'fixes', 'code change'],
question_de: 'Wie funktioniert die Issue-Tracker-Integration?',
question_en: 'How does the issue tracker integration work?',
answer_de: 'Wenn das Code-Scanning (SAST/DAST) oder Pentesting Schwachstellen findet, erstellt die Plattform automatisch Tickets im Issue-Tracker deiner Wahl — Jira, GitLab, Linear, Gitea — mit exakten Code-Änderungsvorschlaegen, welche Datei, welche Zeile, welcher Fix. Die KI kann den Fix auch direkt implementieren. So schliesst sich der Kreis von Finding zu Fix vollständig automatisiert.',
answer_en: 'When code scanning (SAST/DAST) or pentesting finds vulnerabilities, the platform automatically creates tickets in the issue tracker of your choice — Jira, GitLab, Linear, Gitea — with exact code change suggestions: which file, which line, which fix. The AI can also implement the fix directly. This closes the loop from finding to fix fully automated.',
goto_slide: 'how-it-works',
priority: 7,
},
@@ -137,8 +137,8 @@ export const PRESENTER_FAQ: FAQEntry[] = [
keywords: ['meeting', 'recorder', 'aufzeichnung', 'recording', 'nvidia', 'transkription', 'transcription', 'protokoll', 'minutes', 'tasks', 'aufgaben'],
question_de: 'Wie funktioniert der Meeting-Recorder?',
question_en: 'How does the meeting recorder work?',
answer_de: 'Unser NVIDIA-basierter Meeting-Recorder zeichnet Besprechungen auf, transkribiert sie automatisch und extrahiert Aufgaben und Beschluesse. Diese werden direkt als Jira-Tasks angelegt — inklusive Zuweisung, Deadline und Kontext aus dem Meeting. So geht nichts mehr verloren.',
answer_en: 'Our NVIDIA-based meeting recorder records meetings, transcribes them automatically and extracts tasks and decisions. These are directly created as Jira tasks — including assignment, deadline and context from the meeting. Nothing gets lost.',
answer_de: 'Unser NVIDIA-basierter Meeting-Recorder zeichnet Besprechungen auf, transkribiert sie automatisch und extrahiert Aufgaben und Beschluesse. Diese werden direkt als Tasks im Issue-Tracker deiner Wahl angelegt — inklusive Zuweisung, Deadline und Kontext aus dem Meeting. So geht nichts mehr verloren.',
answer_en: 'Our NVIDIA-based meeting recorder records meetings, transcribes them automatically and extracts tasks and decisions. These are directly created as tasks in the issue tracker of your choice — including assignment, deadline and context from the meeting. Nothing gets lost.',
priority: 6,
},
{
@@ -210,8 +210,8 @@ export const PRESENTER_FAQ: FAQEntry[] = [
keywords: ['proliance', 'dataguard', 'heydata', 'vergleich', 'comparison', 'versus'],
question_de: 'Warum können Proliance und DataGuard das nicht?',
question_en: 'Why can\'t Proliance and DataGuard do this?',
answer_de: 'Proliance, DataGuard und heyData fokussieren auf organisatorische DSGVO-Compliance — Verarbeitungsverzeichnisse, Datenschutzerklaerungen, Schulungen. Keiner bietet Code-Scanning, CE-Risikobewertung, Pentesting oder automatische Jira-Integration mit Code-Fixes. Sie machen das Unternehmen teilweise compliant, aber nicht die Produkte. Und keiner hostet die KI ausschliesslich in Europa.',
answer_en: 'Proliance, DataGuard and heyData focus on organizational GDPR compliance — records of processing, privacy policies, training. None offer code scanning, CE risk assessment, pentesting or automatic Jira integration with code fixes. They make the company partially compliant, but not the products. And none host the AI exclusively in Europe.',
answer_de: 'Proliance, DataGuard und heyData fokussieren auf organisatorische DSGVO-Compliance — Verarbeitungsverzeichnisse, Datenschutzerklaerungen, Schulungen. Keiner bietet Code-Scanning, CE-Risikobewertung, Pentesting oder automatische Issue-Tracker-Integration mit Code-Fixes. Sie machen das Unternehmen teilweise compliant, aber nicht die Produkte. Und keiner hostet die KI ausschliesslich in Europa.',
answer_en: 'Proliance, DataGuard and heyData focus on organizational GDPR compliance — records of processing, privacy policies, training. None offer code scanning, CE risk assessment, pentesting or automatic issue tracker integration with code fixes. They make the company partially compliant, but not the products. And none host the AI exclusively in Europe.',
goto_slide: 'competition',
priority: 8,
},
@@ -232,8 +232,8 @@ export const PRESENTER_FAQ: FAQEntry[] = [
keywords: ['cloud', 'mini', 'studio', 'unterschied', 'difference', 'vergleich', 'comparison', 'tier', 'tiers', 'varianten', 'variants'],
question_de: 'Cloud oder Hardware?',
question_en: 'Cloud or hardware?',
answer_de: 'Cloud ist der Standard: BSI-zertifiziert in Deutschland oder OVH in Frankreich. Fixe oder flexible Kosten, modulare Module, Jira-Integration, Matrix/Jitsi, Compliance-LLM. Für Kleinstunternehmen unter 10 Mitarbeitern mit absolutem Privacy-Bedarf bieten wir optional einen vorkonfigurierten Mac Mini mit kleineren lokalen LLMs.',
answer_en: 'Cloud is the standard: BSI-certified in Germany or OVH in France. Fixed or flexible costs, modular modules, Jira integration, Matrix/Jitsi, compliance LLM. For micro businesses under 10 employees with absolute privacy needs, we optionally offer a pre-configured Mac Mini with smaller local LLMs.',
answer_de: 'Cloud ist der Standard: BSI-zertifiziert in Deutschland oder Frankreich. Fixe oder flexible Kosten, modulare Module, Integration in den Issue-Tracker deiner Wahl, Matrix/Jitsi, Compliance-LLM. Für Kleinstunternehmen unter 10 Mitarbeitern mit absolutem Privacy-Bedarf bieten wir optional einen vorkonfigurierten Mac Mini oder Mac Studio mit kleineren lokalen LLMs.',
answer_en: 'Cloud is the standard: BSI-certified in Germany or France. Fixed or flexible costs, modular modules, integration with the issue tracker of your choice, Matrix/Jitsi, compliance LLM. For micro businesses under 10 employees with absolute privacy needs, we optionally offer a pre-configured Mac Mini or Mac Studio with smaller local LLMs.',
goto_slide: 'product',
priority: 8,
},
@@ -244,7 +244,7 @@ export const PRESENTER_FAQ: FAQEntry[] = [
question_en: 'What does the revenue plan look like?',
answer_de: 'Der Fokus liegt auf dem Cloud-Produkt als Umsatztreiber (1.499 EUR/Monat). Rein recurring — monatliche Subscriptions. Die Cloud-Plattform skaliert ohne Hardware-Logistik. Mac Mini/Studio sind Nebenprodukte für spezifische Kundenbeduerfnisse.',
answer_en: 'The focus is on the cloud product as revenue driver (EUR 1,499/month). Purely recurring — monthly subscriptions. The cloud platform scales without hardware logistics. Mac Mini/Studio are side products for specific customer needs.',
goto_slide: 'financials',
goto_slide: 'annex-finanzplan',
priority: 8,
},
{
@@ -252,8 +252,8 @@ export const PRESENTER_FAQ: FAQEntry[] = [
keywords: ['unit economics', 'marge', 'margin', 'ltv', 'cac', 'amortisation', 'amortization'],
question_de: 'Wie sind die Unit Economics?',
question_en: 'What are the unit economics?',
answer_de: 'Bruttomarge über 80% beim Cloud-Produkt — keine Hardware-Kosten. Die AI-First Architektur haelt die operativen Kosten pro Kunde extrem niedrig. Europaeisches Hosting bei SysEleven/OVH/Hetzner ist deutlich günstiger als AWS/Azure. LTV/CAC verbessert sich durch die Plattform-Stickiness: modulare Plattform schaffen natürlichen Lock-in.',
answer_en: 'Gross margin above 80% on the cloud product — no hardware costs. The AI-first architecture keeps operational costs per customer extremely low. European hosting at SysEleven/OVH/Hetzner is significantly cheaper than AWS/Azure. LTV/CAC improves through platform stickiness: all modules create natural lock-in.',
answer_de: 'Bruttomarge über 80% beim Cloud-Produkt — keine Hardware-Kosten. Die AI-First Architektur haelt die operativen Kosten pro Kunde extrem niedrig. Europaeisches Hosting bei SysEleven und Hetzner ist deutlich günstiger als AWS/Azure. LTV/CAC verbessert sich durch die Plattform-Stickiness: modulare Plattform schaffen natürlichen Lock-in.',
answer_en: 'Gross margin above 80% on the cloud product — no hardware costs. The AI-first architecture keeps operational costs per customer extremely low. European hosting at SysEleven and Hetzner is significantly cheaper than AWS/Azure. LTV/CAC improves through platform stickiness: all modules create natural lock-in.',
goto_slide: 'business-model',
priority: 7,
},
@@ -264,7 +264,7 @@ export const PRESENTER_FAQ: FAQEntry[] = [
question_en: 'How does the business model scale?',
answer_de: 'Das Cloud-SDK-Modell skaliert hervorragend: Neue Kunden brauchen nur einen neuen Namespace — keine Hardware-Beschaffung, kein Versand, kein Onsite-Setup. KI-Agenten automatisieren Compliance-Analyse und Code-Scanning. Die isolierten Namespaces ermoeglichen lineare Skalierung der Infrastruktur.',
answer_en: 'The cloud SDK model scales excellently: new customers only need a new namespace — no hardware procurement, no shipping, no onsite setup. AI agents automate compliance analysis and code scanning. The isolated namespaces enable linear infrastructure scaling.',
goto_slide: 'financials',
goto_slide: 'annex-finanzplan',
priority: 7,
},
@@ -296,8 +296,8 @@ export const PRESENTER_FAQ: FAQEntry[] = [
keywords: ['use of funds', 'wofuer', 'what for', 'verwendung', 'allocation', 'mittelverwendung'],
question_de: 'Wofür wird das Kapital verwendet?',
question_en: 'What will the capital be used for?',
answer_de: 'Vier Bereiche: 1) Cloud-Infrastruktur — Skalierung der europäischen Server-Kapazitaet bei SysEleven/OVH/Hetzner. 2) Engineering — weitere Module und Integrationen. 3) Vertrieb — Pilotkunden bei Maschinenbauern, CE-Zertifizierern und produzierenden Unternehmen. 4) Reserve — regulatorische Anforderungen und Working Capital.',
answer_en: 'Four areas: 1) Cloud infrastructure — scaling European server capacity at SysEleven/OVH/Hetzner. 2) Engineering — additional modules and integrations. 3) Sales — pilot customers among machine builders, CE certifiers and producing companies. 4) Reserve — regulatory requirements and working capital.',
answer_de: 'Vier Bereiche: 1) Cloud-Infrastruktur — Skalierung der europäischen Server-Kapazitaet bei SysEleven/Hetzner. 2) Engineering — weitere Module und Integrationen. 3) Vertrieb — Pilotkunden bei Maschinenbauern, CE-Zertifizierern und produzierenden Unternehmen. 4) Reserve — regulatorische Anforderungen und Working Capital.',
answer_en: 'Four areas: 1) Cloud infrastructure — scaling European server capacity at SysEleven/Hetzner. 2) Engineering — additional modules and integrations. 3) Sales — pilot customers among machine builders, CE certifiers and producing companies. 4) Reserve — regulatory requirements and working capital.',
goto_slide: 'the-ask',
priority: 8,
},
@@ -308,7 +308,7 @@ export const PRESENTER_FAQ: FAQEntry[] = [
question_en: 'How long does the capital last?',
answer_de: 'Die Pre-Seed Runde finanziert uns bis zur nächsten Finanzierungsrunde. Als Cloud-Plattform ist unsere Burn Rate niedriger als bei Hardware-lastigen Modellen — kein Lagerbestand, keine Logistik. Die europäische Hosting-Infrastruktur ist deutlich günstiger als US-Cloud-Provider.',
answer_en: 'The pre-seed round funds us until the next financing round. As a cloud platform our burn rate is lower than hardware-heavy models — no inventory, no logistics. European hosting infrastructure is significantly cheaper than US cloud providers.',
goto_slide: 'financials',
goto_slide: 'annex-finanzplan',
priority: 7,
},
{
@@ -328,8 +328,8 @@ export const PRESENTER_FAQ: FAQEntry[] = [
keywords: ['cra', 'cyber resilience', 'cyber resilience act', 'firmware', 'produktsicherheit', 'product security'],
question_de: 'Was ist der Cyber Resilience Act?',
question_en: 'What is the Cyber Resilience Act?',
answer_de: 'Der CRA verpflichtet Hersteller, Software in ihren Produkten abzusichern — über den gesamten Lebenszyklus. Für produzierende Unternehmen mit Firmware, embedded Software und Elektronik bedeutet das: Vulnerability Management, SBOM, Incident Reporting. Unsere Plattform automatisiert all das — vom Repo-Scan bis zum Jira-Ticket mit Code-Fix.',
answer_en: 'The CRA obligates manufacturers to secure software in their products — throughout the entire lifecycle. For producing companies with firmware, embedded software and electronics this means: vulnerability management, SBOM, incident reporting. Our platform automates all of this — from repo scan to Jira ticket with code fix.',
answer_de: 'Der CRA verpflichtet Hersteller, Software in ihren Produkten abzusichern — über den gesamten Lebenszyklus. Für produzierende Unternehmen mit Firmware, embedded Software und Elektronik bedeutet das: Vulnerability Management, SBOM, Incident Reporting. Unsere Plattform automatisiert all das — vom Repo-Scan bis zum Ticket im Issue-Tracker deiner Wahl mit Code-Fix.',
answer_en: 'The CRA obligates manufacturers to secure software in their products — throughout the entire lifecycle. For producing companies with firmware, embedded software and electronics this means: vulnerability management, SBOM, incident reporting. Our platform automates all of this — from repo scan to ticket in your issue tracker with code fix.',
goto_slide: 'annex-regulatory',
priority: 7,
},
@@ -384,8 +384,8 @@ export const PRESENTER_FAQ: FAQEntry[] = [
keywords: ['pentesting', 'penetrationstest', 'penetration test', 'security testing', 'pentests'],
question_de: 'Wie funktioniert das Pentesting?',
question_en: 'How does pentesting work?',
answer_de: 'Pentesting ist fester Bestandteil unserer Code/CE-Saule. Automatisierte Penetrationstests laufen gegen die Kunden-Anwendungen und -Infrastruktur. Gefundene Schwachstellen werden automatisch als Jira-Tickets mit konkreten Code-Änderungsvorschlaegen erstellt — die KI kann die Fixes direkt implementieren. So wird der gesamte Zyklus von Finding bis Fix automatisiert.',
answer_en: 'Pentesting is a core part of our code/CE pillar. Automated penetration tests run against customer applications and infrastructure. Found vulnerabilities are automatically created as Jira tickets with specific code change suggestions — the AI can implement fixes directly. This automates the entire cycle from finding to fix.',
answer_de: 'Pentesting ist fester Bestandteil unserer Code/CE-Saule. Automatisierte Penetrationstests laufen gegen die Kunden-Anwendungen und -Infrastruktur. Gefundene Schwachstellen werden automatisch als Tickets im Issue-Tracker deiner Wahl mit konkreten Code-Änderungsvorschlaegen erstellt — die KI kann die Fixes direkt implementieren. So wird der gesamte Zyklus von Finding bis Fix automatisiert.',
answer_en: 'Pentesting is a core part of our code/CE pillar. Automated penetration tests run against customer applications and infrastructure. Found vulnerabilities are automatically created as tickets in the issue tracker of your choice with specific code change suggestions — the AI can implement fixes directly. This automates the entire cycle from finding to fix.',
goto_slide: 'how-it-works',
priority: 7,
},
@@ -396,8 +396,8 @@ export const PRESENTER_FAQ: FAQEntry[] = [
keywords: ['demo', 'test', 'testen', 'try', 'ausprobieren', 'live', 'showcase'],
question_de: 'Kann ich eine Demo sehen?',
question_en: 'Can I see a demo?',
answer_de: 'Sehr gerne! Wir zeigen Ihnen die Cloud-Plattform live — inklusive Code-Scanning, Compliance-Module, KI-Analyse, Jira-Integration und den Meeting-Recorder. Ein Cloud-Demo-Zugang kann sofort bereitgestellt werden. Kontaktieren Sie uns für einen Termin.',
answer_en: 'Absolutely! We will show you the cloud platform live — including code scanning, compliance modules, AI analysis, Jira integration and the meeting recorder. A cloud demo access can be provisioned immediately. Contact us for an appointment.',
answer_de: 'Sehr gerne! Wir zeigen Ihnen die Cloud-Plattform live — inklusive Code-Scanning, Compliance-Module, KI-Analyse, Issue-Tracker-Integration und den Meeting-Recorder. Ein Cloud-Demo-Zugang kann sofort bereitgestellt werden. Kontaktieren Sie uns für einen Termin.',
answer_en: 'Absolutely! We will show you the cloud platform live — including code scanning, compliance modules, AI analysis, issue tracker integration and the meeting recorder. A cloud demo access can be provisioned immediately. Contact us for an appointment.',
priority: 6,
},
{
@@ -460,11 +460,11 @@ export const PRESENTER_FAQ: FAQEntry[] = [
{
id: 'investment-captable',
keywords: ['bewertung', 'valuation', 'cap table', 'anteile', 'shares', 'equity', 'invest', 'pre-money', 'post-money', 'wie viel prozent', 'how much percent', 'verwässerung', 'dilution'],
question_de: 'Wie ist die Bewertung und wie sieht der Cap Table aus?',
question_en: 'What is the valuation and cap table?',
answer_de: 'Wir gehen mit einer Pre-Money-Bewertung von 4 Millionen Euro in die Pre-Seed-Runde. Bei einem Investment von 975.000 Euro ergibt sich eine Post-Money-Bewertung von knapp 5 Millionen Euro. Der Investor erhält dafür etwa 19,6 Prozent der Anteile. Die beiden Gründer halten zusammen 75 Prozent, wobei ein ESOP-Pool von 5,4 Prozent für Schlüsselmitarbeiter vorgesehen ist, die in der frühen Phase unter Marktgehalt einsteigen. Besonders attraktiv für Business Angels: Über das INVEST-Programm des BAFA erhalten Investoren 20 Prozent ihres Investments als staatlichen Zuschuss zurück — das sind bei 975.000 Euro Investment ganze 195.000 Euro nicht rückzahlbarer Zuschuss. Die einzige Bedingung ist eine Haltefrist von drei Jahren.',
answer_en: 'We enter the pre-seed round with a pre-money valuation of 4 million euros. With an investment of 975,000 euros, this results in a post-money valuation of just under 5 million euros. The investor receives approximately 19.6 percent of the shares. Both founders hold 75 percent together, with a 5.4 percent ESOP pool reserved for key employees joining in the early phase below market salary. Particularly attractive for business angels: through the BAFA INVEST program, investors receive 20 percent of their investment back as a government grant — that is 195,000 euros non-repayable on a 975,000 euro investment. The only condition is a three-year holding period.',
goto_slide: 'cap-table',
question_de: 'Wie funktioniert das Investment beim Wandeldarlehen?',
question_en: 'How does the convertible loan investment work?',
answer_de: 'Wir finanzieren über ein Wandeldarlehen im Rahmen des Pre-Seed-BW-Programms der L-Bank. Die Gesamtfinanzierung beträgt 200.000 Euro: 40.000 Euro vom privaten Investor und 160.000 Euro als staatliche Co-Finanzierung durch die L-Bank. Das Wandeldarlehen hat keine sofortige Bewertung — bei der nächsten qualifizierenden Finanzierungsrunde wandelt es automatisch in Anteile, typischerweise mit einem Discount für den Frühphasen-Investor. Der Vorteil: weniger Kapitaleinsatz bei starkem Hebel durch die staatliche Co-Finanzierung. Die Höhe ist flexibel — auch 250.000 oder bis zu 400.000 Euro Gesamtfinanzierung sind möglich. Besonders attraktiv: Über das BAFA INVEST-Programm erhalten Investoren bis zu 15 Prozent ihres Investments als steuerfreien Erwerbszuschuss zurück, plus 25 Prozent Exit-Zuschuss auf Veräußerungsgewinne.',
answer_en: 'We finance through a convertible loan under the L-Bank Pre-Seed BW program. Total funding is EUR 200,000: EUR 40,000 from the private investor and EUR 160,000 as government co-financing from L-Bank. The convertible loan has no immediate valuation — at the next qualifying funding round, it automatically converts to equity, typically with a discount for the early-stage investor. The advantage: less capital deployed with strong leverage through government co-financing. The amount is flexible — EUR 250,000 or up to EUR 400,000 total funding is also possible. Particularly attractive: through the BAFA INVEST program, investors receive up to 15 percent of their investment as a tax-free acquisition grant, plus 25 percent exit grant on capital gains.',
goto_slide: 'the-ask',
priority: 10,
},
{
@@ -472,20 +472,74 @@ export const PRESENTER_FAQ: FAQEntry[] = [
keywords: ['gewinn', 'profit', 'ausschüttung', 'dividende', 'dividend', 'reinvestition', 'reinvestment', 'gehalt gründer', 'founder salary', 'wann verdienen', 'when earn'],
question_de: 'Was passiert mit den Gewinnen?',
question_en: 'What happens with the profits?',
answer_de: 'Bei schnell wachsenden SaaS-Startups ist der Standard klar: Gewinne werden zu 100 Prozent reinvestiert, nicht ausgeschüttet. Jeder Euro, der heute in Wachstum fließt, bringt in zwei bis drei Jahren den drei- bis fünffachen Return. Eine Ausschüttung wäre in dieser Phase wirtschaftlich nicht sinnvoll und würde bei Investoren ein rotes Signal auslösen. Die Gründer partizipieren über die Wertsteigerung ihrer Equity-Anteile, nicht über das Gehalt. Im ersten Jahr arbeiten die Gründer unbezahlt, ab 2027 gibt es 7.000 Euro brutto pro Monat — bewusst unter Marktniveau, um Kapitaldisziplin zu zeigen. Ab dem Break-Even 2029 steigt das Gehalt moderat auf rund 9.000 Euro. Das ist der gleiche Ansatz, den Vanta, Snyk und DataGuard in ihrer Frühphase verfolgt haben.',
answer_en: 'For fast-growing SaaS startups, the standard is clear: profits are 100 percent reinvested, not distributed. Every euro invested in growth today returns three to five times in two to three years. Distribution at this stage would not make economic sense and would be a red flag for investors. The founders participate through the appreciation of their equity stakes, not through salary. In the first year, founders work unpaid. From 2027, they receive 7,000 euros gross per month — deliberately below market level to show capital discipline. From break-even in 2029, salary increases moderately to about 9,000 euros. This is the same approach that Vanta, Snyk and DataGuard took in their early phases.',
goto_slide: 'cap-table',
answer_de: 'Gewinne werden zu 100 Prozent reinvestiert. Die Gründer arbeiten bis zur Gründung im August 2026 unbezahlt. Ab Oktober 2026 gibt es 3.000 Euro brutto pro Monat — bewusst weit unter Marktniveau, um Kapitaldisziplin zu zeigen. Personal wird nur eingestellt, wenn der nächste Umsatzmeilenstein es rechtfertigt. Jeder Euro geht in Wachstum, nicht in Gehälter oder Ausschüttungen.',
answer_en: 'Profits are 100 percent reinvested. The founders work unpaid until incorporation in August 2026. From October 2026, they receive EUR 3,000 gross per month — deliberately well below market level to show capital discipline. Staff is only hired when the next revenue milestone justifies it. Every euro goes into growth, not into salaries or distributions.',
goto_slide: 'the-ask',
priority: 9,
},
// === WANDELDARLEHEN & FÖRDER-KOMBINATION ===
{
id: 'invest-wandeldarlehen-preseed',
keywords: ['wandeldarlehen', 'convertible', 'darlehen', 'loan', 'pre-seed', 'l-bank', 'lbank', 'bw', 'baden-württemberg', 'conversion', 'wandlung', 'umwandlung'],
question_de: 'Wie funktioniert das Wandeldarlehen mit Pre-Seed BW?',
question_en: 'How does the convertible loan work with Pre-Seed BW?',
answer_de: 'Unser Wandeldarlehen ist über das Pre-Seed-Programm von Start-up BW / L-Bank strukturiert. Der private Investor beteiligt sich mit mindestens 20 Prozent der Gesamtsumme (ab 40.000 Euro), die L-Bank stellt die restlichen bis zu 80 Prozent als Zuwendung mit Wandlungsvorbehalt bereit. Bei einer Gesamtfinanzierung von 200.000 Euro bedeutet das: 40.000 Euro vom Investor und 160.000 Euro von der L-Bank. Optional ist auch eine doppelte Tranche mit 400.000 Euro Gesamtfinanzierung möglich (80.000 Euro Investor, 320.000 Euro L-Bank). Das Wandeldarlehen hat keine sofortige Bewertung — bei der nächsten qualifizierenden Finanzierungsrunde wandelt es automatisch in Anteile, typischerweise mit einem Discount für den Frühphasen-Investor. Der Vorteil für den Investor: weniger Kapitaleinsatz bei gleichzeitig starkem Hebel durch die staatliche Co-Finanzierung, und keine sofortige Verwässerung der Gründeranteile.',
answer_en: 'Our convertible loan is structured through the Pre-Seed BW / L-Bank program. The private investor contributes at least 20 percent of the total amount (from EUR 40,000), while L-Bank provides the remaining up to 80 percent as a grant with conversion option. For a total funding of EUR 200,000, this means EUR 40,000 from the investor and EUR 160,000 from L-Bank. Optionally, a double tranche of EUR 400,000 total is possible (EUR 80,000 investor, EUR 320,000 L-Bank). The convertible loan has no immediate valuation — at the next qualifying funding round, it automatically converts to equity, typically with a discount for the early-stage investor. The advantage for the investor: less capital deployed with strong leverage through government co-financing, and no immediate dilution of founder shares.',
goto_slide: 'the-ask',
priority: 10,
},
{
id: 'invest-bafa-preseed-kombination',
keywords: ['bafa', 'invest', 'kombination', 'combination', 'kompatibel', 'compatible', 'förderung', 'funding', 'zuschuss', 'grant', 'kombinierbar', 'combinable', 'förderfähig', 'eligible'],
question_de: 'Kann man BAFA INVEST und Pre-Seed BW kombinieren?',
question_en: 'Can BAFA INVEST and Pre-Seed BW be combined?',
answer_de: 'Grundsätzlich ja, aber nur unter klaren strukturellen Bedingungen. Beide Programme haben unterschiedliche Anforderungen an das Finanzierungsinstrument: BAFA INVEST fördert den privaten Investor mit bis zu 15 Prozent Erwerbszuschuss, bevorzugt aber Eigenkapital oder eigenkapitalähnliche Instrumente. Die L-Bank Pre-Seed verlangt explizit ein Wandeldarlehen. Das bedeutet: Nicht jedes Standard-Wandeldarlehen ist automatisch BAFA-INVEST-förderfähig. Damit die Kombination funktioniert, muss das Wandeldarlehen BAFA-konform ausgestaltet sein — also klare Conversion-Regeln haben und wirtschaftlich wie Equity wirken. Zusätzliche BAFA-Voraussetzungen: der Investor muss eine natürliche Person sein, darf kein Bestandsgesellschafter sein, das Mindestinvestment beträgt 10.000 Euro, die Mindesthaltedauer ist 3 Jahre, und das Unternehmen muss vor dem Investment BAFA-zertifiziert sein. Wir empfehlen, die konkrete Kombinierbarkeit für den jeweiligen Fall mit dem BAFA und dem L-Bank-Betreuungspartner abzustimmen, bevor das Termsheet finalisiert wird. Richtig aufgesetzt, profitiert der Investor von bis zu 15 Prozent Erwerbszuschuss plus 25 Prozent Exit-Zuschuss — zusätzlich zum Hebel durch die L-Bank-Co-Finanzierung.',
answer_en: 'In principle yes, but only under clear structural conditions. Both programs have different requirements for the financial instrument: BAFA INVEST supports the private investor with up to 15 percent acquisition grant but prefers equity or equity-like instruments. L-Bank Pre-Seed explicitly requires a convertible loan. This means: not every standard convertible loan is automatically eligible for BAFA INVEST. For the combination to work, the convertible loan must be structured in a BAFA-compliant way — with clear conversion rules and economic characteristics similar to equity. Additional BAFA requirements: the investor must be a natural person, must not be an existing shareholder, minimum investment is EUR 10,000, minimum holding period is 3 years, and the company must be BAFA-certified before the investment. We recommend coordinating the specific combinability with BAFA and the L-Bank supervising partner before finalizing the term sheet. Properly structured, the investor benefits from up to 15 percent acquisition grant plus 25 percent exit grant — on top of the leverage from L-Bank co-financing.',
goto_slide: 'the-ask',
priority: 10,
},
// === FINANZIERUNGSSTRATEGIE ===
{
id: 'invest-2nd-round',
keywords: ['2. finanzierungsrunde', 'zweite runde', 'second round', '500.000', '500k', 'warum mehr geld', 'why more money', 'optional', 'zusätzlich', 'additional funding', 'serie', 'series'],
question_de: 'Warum plant ihr eine 2. Finanzierungsrunde mit 500.000 Euro in 2028?',
question_en: 'Why do you plan a 2nd funding round of EUR 500,000 in 2028?',
answer_de: 'Die 2. Finanzierungsrunde ist bewusst als optional gekennzeichnet. Unser Finanzplan zeigt, dass wir Ende 2027 eine Liquidität von knapp 470.000 Euro haben und der rechnerische Fehlbetrag in 2028 bei nur rund 30.000 Euro liegt — das könnten wir auch privat oder durch kleinere Maßnahmen kompensieren. Die Strategie dahinter: Wir wollen so wenig Wandeldarlehen wie möglich aufnehmen, dann anhand der tatsächlichen Markttraktion prüfen, wie erfolgreich wir sind, und erst dann entscheiden, ob und wie viel zusätzliches Kapital sinnvoll ist. Die 500.000 Euro wären eine Eigenkapitalrunde (kein Wandeldarlehen) und würden primär dazu dienen, schneller Personal einzustellen und ab 2028 deutlich mehr Kunden zu akquirieren. Es gibt auch einen flexiblen Mittelweg: Statt 200.000 Euro Wandeldarlehen könnten wir z.B. 250.000 Euro aufnehmen und hätten den Liquiditätsengpass gar nicht erst. All das sind Punkte, die wir gerne gemeinsam mit dem Investor besprechen — die optimale Struktur hängt von der Risikobereitschaft und den strategischen Zielen beider Seiten ab.',
answer_en: 'The 2nd funding round is deliberately marked as optional. Our financial plan shows cash of nearly EUR 470,000 at end of 2027, with the calculated shortfall in 2028 at only around EUR 30,000 — which we could compensate privately or through smaller measures. The strategy: we want to take on as little convertible loan as possible, then evaluate our actual market traction, and only then decide whether and how much additional capital makes sense. The EUR 500,000 would be an equity round (not a convertible loan) and would primarily serve to hire faster and acquire significantly more customers from 2028. There is also a flexible middle ground: instead of EUR 200,000 convertible loan, we could raise e.g. EUR 250,000 and avoid the liquidity gap entirely. These are all points we would like to discuss together with the investor — the optimal structure depends on both sides\' risk appetite and strategic goals.',
goto_slide: 'the-ask',
priority: 10,
},
{
id: 'invest-flexible-wd',
keywords: ['flexibel', 'flexible', '200.000', '400.000', '250.000', 'wandeldarlehen höhe', 'convertible amount', 'wie viel wandeldarlehen', 'höhe investment', 'investment size', 'tranche'],
question_de: 'Ist die Höhe des Wandeldarlehens flexibel?',
question_en: 'Is the convertible loan amount flexible?',
answer_de: 'Ja, absolut. Unser Basis-Szenario geht von 200.000 Euro Gesamtfinanzierung aus (40.000 Euro Investor + 160.000 Euro L-Bank). Das Pre-Seed-BW-Programm erlaubt aber auch eine doppelte Tranche von bis zu 400.000 Euro (80.000 Euro Investor + 320.000 Euro L-Bank) oder jeden Betrag dazwischen. Der Investor entscheidet, wie viel er einbringen möchte — die L-Bank kofinanziert dann anteilig. Ein höheres initiales Investment (z.B. 250.000 statt 200.000 Euro) würde den kleinen Liquiditätsengpass in 2028 vollständig vermeiden und könnte die 2. Finanzierungsrunde überflüssig machen. Unser Ansatz ist konservativ: lieber mit wenig starten, Traktion beweisen, und dann auf Basis von echten Zahlen entscheiden.',
answer_en: 'Yes, absolutely. Our base scenario assumes EUR 200,000 total funding (EUR 40,000 investor + EUR 160,000 L-Bank). However, the Pre-Seed BW program also allows a double tranche of up to EUR 400,000 (EUR 80,000 investor + EUR 320,000 L-Bank), or any amount in between. The investor decides how much to contribute — L-Bank co-finances proportionally. A higher initial investment (e.g. EUR 250,000 instead of EUR 200,000) would fully eliminate the small liquidity gap in 2028 and could make the 2nd funding round unnecessary. Our approach is conservative: start with less, prove traction, then decide based on real numbers.',
goto_slide: 'the-ask',
priority: 9,
},
{
id: 'invest-capital-discipline',
keywords: ['kapitaldisziplin', 'capital discipline', 'lean', 'sparsam', 'frugal', 'burn rate', 'runway', 'wie lange reicht', 'how long lasts', 'minimal investment'],
question_de: 'Wie stellt ihr Kapitaldisziplin sicher?',
question_en: 'How do you ensure capital discipline?',
answer_de: 'Kapitaldisziplin ist ein Kernprinzip unserer Planung. Die Gründer arbeiten im ersten Jahr ohne Gehalt, danach zu einem Bruchteil des Marktgehalts. Wir stellen Personal nur ein, wenn der nächste Umsatzmeilenstein das rechtfertigt — jede Position hat ein konkretes Start-Datum, das an den Geschäftsverlauf gekoppelt ist. Wir nutzen Open-Source-Technologie konsequent (PostgreSQL, Qdrant, Gitea statt GitHub Enterprise), hosten auf deutschem Mittelstands-Hosting (SysEleven, Hetzner) statt teurem AWS/Azure, und haben die Bürokosten auf ein Minimum reduziert. Unser Ziel ist es, mit möglichst wenig externem Kapital profitabel zu werden — nicht weil wir kein Geld aufnehmen wollen, sondern weil Kapitaldisziplin in der Frühphase der stärkste Vertrauensbeweis gegenüber Investoren ist.',
answer_en: 'Capital discipline is a core principle of our planning. Founders work unpaid in the first year, then at a fraction of market salary. We only hire when the next revenue milestone justifies it — each position has a concrete start date tied to business progress. We consistently use open-source technology (PostgreSQL, Qdrant, Gitea instead of GitHub Enterprise), host on German mid-market hosting (SysEleven, Hetzner) instead of expensive AWS/Azure, and minimized office costs. Our goal is to become profitable with as little external capital as possible — not because we do not want to raise, but because capital discipline in the early phase is the strongest trust signal for investors.',
goto_slide: 'the-ask',
priority: 8,
},
// === TEAM & PERSONALAUFBAU ===
{
id: 'team-structure',
keywords: ['team', 'mitarbeiter', 'employees', 'personalaufbau', 'hiring', 'rollen', 'roles', 'organigramm', 'organization', 'abteilungen', 'departments', 'headcount'],
question_de: 'Wie ist das Team aufgebaut?',
question_en: 'How is the team structured?',
answer_de: 'Wir bauen das Team über vier Jahre von 5 auf 35 Mitarbeiter auf. Dabei orientieren wir uns an den Einstellungsmustern erfolgreicher Compliance-SaaS-Unternehmen wie DataGuard, Vanta und heyData. Der größte Bereich ist die Softwareentwicklung mit 13 Personen, das sind rund 37 Prozent. Danach kommt der Vertrieb mit 7 Mitarbeitern, also etwa 20 Prozent. Customer Success, Compliance und Legal sowie Marketing besetzen wir jeweils mit 3 Personen. Dazu kommen Product Management und Operations mit je 2 Personen. Ganz bewusst stellen wir als allererste Person nach den Gründern einen zertifizierten Compliance Consultant ein, noch vor den ersten Entwicklern. Der Grund ist einfach: Im deutschen Mittelstand kauft niemand Compliance-Software von einem reinen Tech-Team. Domain-Expertise schafft sofortige Glaubwürdigkeit bei den Entscheidern.',
answer_en: 'We grow the team from 5 to 35 over four years, following the hiring patterns of successful compliance SaaS companies like DataGuard, Vanta and heyData. The largest area is engineering with 13 people, about 37 percent. Sales follows with 7 employees, roughly 20 percent. Customer success, compliance and legal, and marketing each get 3 people. Product management and operations each have 2. Our very first hire after the founders is a certified compliance consultant, even before the first developers. The reason is simple: in the German Mittelstand, nobody buys compliance software from a pure tech team. Domain expertise creates instant credibility with decision-makers.',
answer_de: 'Wir bauen das Team bewusst lean auf — von 2 Gründern auf 9 Personen bis 2030. Jede Einstellung ist an einen konkreten Umsatzmeilenstein gekoppelt. Die erste Einstellung nach den Gründern ist ein IT-Recht und Datenschutzjurist auf 50-Prozent-Basis ab Oktober 2026. Der Grund: Im deutschen Mittelstand kauft niemand Compliance-Software von einem reinen Tech-Team. Danach folgen Senior Security Engineer ab 2028, Vertrieb Direktkunden, Backend-Entwickler, Technischer Kundenbetreuer, Marketing Manager und DevOps-Ingenieur. Wir stellen nur ein, wenn der Umsatz es trägt.',
answer_en: 'We deliberately build the team lean — from 2 founders to 9 people by 2030. Each hire is tied to a concrete revenue milestone. The first hire after the founders is an IT law and data protection specialist at 50 percent from October 2026. The reason: in the German Mittelstand, nobody buys compliance software from a pure tech team. Then follow Senior Security Engineer from 2028, direct sales, backend developer, technical account manager, marketing manager and DevOps engineer. We only hire when revenue supports it.',
goto_slide: 'team',
priority: 9,
},
@@ -494,8 +548,8 @@ export const PRESENTER_FAQ: FAQEntry[] = [
keywords: ['einstellungsreihenfolge', 'hiring order', 'wer zuerst', 'who first', 'erste einstellung', 'first hire', 'personalplan', 'staffing plan', 'rekrutierung', 'recruiting'],
question_de: 'In welcher Reihenfolge stellt ihr ein?',
question_en: 'In what order do you hire?',
answer_de: 'Im ersten Jahr starten wir mit fünf Personen: die beiden Gründer, dann ein Senior Security Engineer als allererste Einstellung, ein Full-Stack-Entwickler und ein CE-Software-Risikoingenieur. Der Security Engineer kommt bewusst zuerst, weil der Scanner das Kernprodukt ist. Der CE-Ingenieur ist unser Differentiator — kein Wettbewerber hat diese Rolle. Im zweiten Jahr wachsen wir auf zehn mit dem Channel Manager für die Bechtle-Partnerschaft als strategisch wichtigster Einstellung, dazu ein DevSecOps-Ingenieur, ein Lösungsberater für Partner-Demos, ein technischer Kundenbetreuer und ein KI-Ingenieur. Die Logik: Channel vor Direktvertrieb, weil ein Bechtle-Deal zehn bis fünfzig Endkunden bringt. Im dritten Jahr bauen wir auf siebzehn aus — der erste Direktvertriebler kommt erst jetzt, nachdem der Vertriebsprozess durch Gründer und Channel bewiesen ist. Dazu kommen ein Backend-Entwickler, Marketing, ein Compliance-Jurist für Glaubwürdigkeit, ein Frontend-Entwickler, ein Security-Analyst und ein zweiter Kundenbetreuer. Ab dem vierten Jahr kommen die Führungskräfte: VP Sales übernimmt vom CEO, dazu Plattform-Engineering, Produktmanagement und QA. Im fünften Jahr spezialisieren wir uns mit Enterprise-Vertrieb, EU-Marktentwicklung, einem zweiten KI-Ingenieur für NLP, Supply-Chain-Security, Developer Relations und einem Finanzcontroller für die Series-A-Vorbereitung.',
answer_en: 'In the first year we start with five people: both founders as CEO and CTO, then an experienced compliance consultant with TÜV certification and two full-stack developers. The compliance consultant comes deliberately before the developers because they create instant credibility in customer meetings. In year two we grow to ten: first a sales executive with manufacturing contacts, then a customer success manager, an AI engineer, a head of product and a frontend developer. The logic: sales and customer proximity before more engineering. In year three we expand to seventeen with a second sales executive, a DevOps engineer, a marketing manager, a second compliance consultant, a senior backend developer, a second customer success manager and an SDR. From year four, leadership roles join: VP sales, VP marketing, a solutions engineer for complex deals, a security engineer, HR and event management, plus QA. In year five we further specialize with DACH sales, an ML engineer, developer relations, finance, legal counsel, a partnership manager and an engineering manager.',
answer_de: 'Die Reihenfolge ist bewusst gewählt: Position 3 ist IT-Recht und Datenschutzjurist auf 50-Prozent-Basis ab Oktober 2026 — Domain-Expertise vor Engineering. Position 4 ist ein Senior Security Engineer ab April 2028, wenn die ersten Enterprise-Kunden kommen. Position 5 Vertrieb Direktkunden ab September 2028. Dann Backend-Entwickler, Technischer Kundenbetreuer, Marketing Manager und DevOps-Ingenieur in 2029 und 2030. Jede Einstellung hat ein konkretes Start-Datum, das an den Geschäftsverlauf gekoppelt ist. Wir stellen nur ein, wenn der Umsatz die Position trägt.',
answer_en: 'The hiring order is deliberate: Position 3 is an IT law and data protection specialist at 50 percent from October 2026 — domain expertise before engineering. Position 4 is a Senior Security Engineer from April 2028 when the first enterprise customers arrive. Position 5 is direct sales from September 2028. Then backend developer, technical account manager, marketing manager and DevOps engineer in 2029 and 2030. Each hire has a concrete start date tied to business progress. We only hire when revenue supports the position.',
goto_slide: 'annex-finanzplan',
priority: 8,
},
@@ -530,15 +584,390 @@ export const PRESENTER_FAQ: FAQEntry[] = [
priority: 7,
},
// === FISA 702 / DRITTLANDTRANSFER / EU-SOUVERAENITAET ===
{
id: 'fisa-702-what',
keywords: ['fisa', 'fisa 702', 'surveillance', 'ueberwachung', 'us-zugriff', 'us zugriff', 'nsa', 'prism', 'upstream', 'foreign intelligence'],
question_de: 'Was ist FISA 702 und warum ist das relevant?',
question_en: 'What is FISA 702 and why does it matter?',
answer_de: 'FISA 702 ist ein US-Ueberwachungsgesetz, das US-Behoerden erlaubt, gezielt Daten von Nicht-US-Personen im Ausland zu ueberwachen — ohne individuellen richterlichen Beschluss. Es gibt zwei Hauptprogramme: PRISM, bei dem US-Unternehmen wie Microsoft, Google und Amazon Daten an Behoerden herausgeben muessen, und Upstream, bei dem direkt auf Internet-Infrastruktur zugegriffen wird. Das betrifft jeden EU-Nutzer eines US-Dienstes. Genau deshalb wurde das Privacy Shield durch das Schrems-II-Urteil des EuGH gekippt. Fuer BreakPilot ist das hochrelevant: Jedes Unternehmen, das US-Cloud oder US-KI nutzt, hat ein strukturelles FISA-702-Risiko. Unsere EU-only Architektur eliminiert dieses Risiko vollstaendig.',
answer_en: 'FISA 702 is a US surveillance law that allows US authorities to target data of non-US persons abroad — without individual court orders. There are two main programs: PRISM, where US companies like Microsoft, Google and Amazon must hand over data to authorities, and Upstream, which directly taps internet infrastructure. This affects every EU user of a US service. This is exactly why the EU-US Privacy Shield was invalidated by the Schrems II ruling. For BreakPilot this is highly relevant: every company using US cloud or US AI has a structural FISA 702 risk. Our EU-only architecture eliminates this risk entirely.',
goto_slide: 'annex-regulatory',
priority: 9,
},
{
id: 'fisa-eu-cloud-myth',
keywords: ['eu cloud', 'eu-cloud', 'eu region', 'serverstandort', 'aws eu', 'azure eu', 'google eu', 'rechenzentrum', 'data center', 'frankfurt', 'ireland'],
question_de: 'Schuetzt eine EU-Cloud-Region bei US-Anbietern vor FISA 702?',
question_en: 'Does an EU cloud region at US providers protect against FISA 702?',
answer_de: 'Nein. Das ist einer der groessten Irrtuemer im Markt. Wenn der Cloud-Anbieter ein US-Unternehmen ist — also AWS, Microsoft Azure oder Google Cloud — dann unterliegt er US-Recht, egal wo der Server steht. FISA 702 und der Cloud Act gelten extraterritorial. Das bedeutet: Ein Server in Frankfurt bei AWS ist rechtlich genauso exponiert wie einer in Virginia. Unternehmen zahlen mehr fuer die EU-Region und wiegen sich in falscher Sicherheit. BreakPilot setzt deshalb konsequent auf EU-Anbieter ohne US-Muttergesellschaft — SysEleven und Hetzner, beide BSI-konform und ohne FISA-Exposition.',
answer_en: 'No. This is one of the biggest misconceptions in the market. If the cloud provider is a US company — AWS, Microsoft Azure or Google Cloud — it is subject to US law regardless of where the server is located. FISA 702 and the Cloud Act apply extraterritorially. A server in Frankfurt at AWS is legally just as exposed as one in Virginia. Companies pay more for the EU region and create a false sense of security. BreakPilot therefore exclusively uses EU providers without US parent companies — SysEleven and Hetzner, both BSI-compliant and without FISA exposure.',
goto_slide: 'annex-architecture',
priority: 9,
},
{
id: 'fisa-dsfa-contradiction',
keywords: ['dsfa', 'dpia', 'risikoakzeptanz', 'risk acceptance', 'restrisiko', 'residual risk', 'widerspruch', 'contradiction', 'schoenreden', 'fake compliance'],
question_de: 'Warum reicht eine DSFA alleine nicht aus, um US-Cloud-Risiken zu bewaeltigen?',
question_en: 'Why is a DPIA alone not enough to manage US cloud risks?',
answer_de: 'Das ist ein zentraler Widerspruch im aktuellen Compliance-Markt. Unternehmen erstellen eine Datenschutz-Folgenabschaetzung, dokumentieren das FISA-702-Risiko, treffen Massnahmen wie Verschluesselung und EU-Region — und akzeptieren dann das Restrisiko. Faktisch sagen sie damit: Wir wissen, dass US-Behoerden zugreifen koennten, und nehmen das in Kauf. Das ist kein Sicherheitsnachweis, sondern ein Rechtfertigungsinstrument. Die DSGVO erlaubt das ueber den risikobasierten Ansatz, aber es bleibt eine bewusste Risikoakzeptanz fuer ein technisch nicht loesbares Problem. BreakPilot geht einen fundamental anderen Weg: Wir eliminieren das Restrisiko strukturell, statt es zu dokumentieren. Kein US-Anbieter, keine FISA-Exposition, kein Restrisiko. Das ist der Unterschied zwischen Compliance simulieren und Compliance loesen.',
answer_en: 'This is a central contradiction in the current compliance market. Companies create a Data Protection Impact Assessment, document the FISA 702 risk, implement measures like encryption and EU region — and then accept the residual risk. In effect they are saying: we know US authorities could access the data, and we accept that. This is not a security proof but a justification instrument. GDPR allows this through the risk-based approach, but it remains a deliberate risk acceptance for a technically unsolvable problem. BreakPilot takes a fundamentally different approach: we structurally eliminate the residual risk instead of documenting it. No US provider, no FISA exposure, no residual risk. That is the difference between simulating compliance and solving compliance.',
goto_slide: 'annex-regulatory',
priority: 8,
},
{
id: 'fisa-market-opportunity',
keywords: ['marktchance', 'market opportunity', 'warum eu', 'why eu', 'eu-first', 'souveraenitaet', 'sovereignty', 'datensouveraenitaet', 'digital sovereignty', 'schrems', 'privacy shield'],
question_de: 'Warum ist FISA 702 eine Marktchance fuer BreakPilot?',
question_en: 'Why is FISA 702 a market opportunity for BreakPilot?',
answer_de: 'FISA 702 ist der zentrale Grund, warum EU-US-Datentransfers rechtlich schwierig sind, warum AI-Compliance ein riesiger Markt ist und warum unser EU-first-Ansatz strategischen Vorteil hat. Das Schrems-II-Urteil hat gezeigt, dass politische Loesungen wie Privacy Shield scheitern. Das neue EU-US Data Privacy Framework wird von Experten als naechstes Schrems-III angesehen. Jedes Mal wenn ein solches Abkommen kippt, stehen tausende Unternehmen vor dem Problem, ihre US-Dienste nicht mehr rechtskonform nutzen zu koennen. BreakPilot ist davon nicht betroffen — unsere Architektur ist strukturell unabhaengig von US-Recht. Das ist kein Nice-to-have, sondern konkrete Risikovermeidung fuer unsere Kunden.',
answer_en: 'FISA 702 is the central reason why EU-US data transfers are legally difficult, why AI compliance is a huge market, and why our EU-first approach has strategic advantage. The Schrems II ruling showed that political solutions like Privacy Shield fail. The new EU-US Data Privacy Framework is seen by experts as the next Schrems III. Every time such an agreement falls, thousands of companies face the problem of no longer being able to use their US services in compliance. BreakPilot is not affected — our architecture is structurally independent of US law. This is not a nice-to-have but concrete risk avoidance for our customers.',
goto_slide: 'market',
priority: 8,
},
{
id: 'fisa-breakpilot-architecture',
keywords: ['architektur', 'architecture', 'eu-only', 'kein us', 'no us', 'syseleven', 'hetzner', 'bsi', 'hosting', 'wo gehostet', 'where hosted'],
question_de: 'Wie schuetzt sich BreakPilot konkret gegen FISA 702?',
question_en: 'How does BreakPilot specifically protect against FISA 702?',
answer_de: 'Unsere gesamte Infrastruktur laeuft auf EU-Anbietern ohne US-Muttergesellschaft. Wir nutzen SysEleven und Hetzner — beide BSI-konform und in Deutschland. Unsere LLMs laufen lokal oder auf BSI-zertifizierten EU-Servern. Keine personenbezogenen Daten verlassen jemals die EU. Wir setzen keine US-KI-Dienste wie OpenAI, Anthropic Cloud oder Google AI ein. Isolierte Namespaces pro Kunde stellen sicher, dass Daten strikt getrennt sind. Die Schluesselhoheit liegt vollstaendig beim Kunden. Damit ist FISA 702 fuer unsere Kunden schlicht nicht anwendbar — es gibt keinen US-Anbieter in der Kette, der zur Herausgabe verpflichtet werden koennte.',
answer_en: 'Our entire infrastructure runs on EU providers without US parent companies. We use SysEleven and Hetzner — both BSI-compliant and located in Germany. Our LLMs run locally or on BSI-certified EU servers. No personal data ever leaves the EU. We do not use US AI services like OpenAI, Anthropic Cloud or Google AI. Isolated namespaces per customer ensure strict data separation. Key management is entirely under customer control. This makes FISA 702 simply inapplicable for our customers — there is no US provider in the chain that could be compelled to hand over data.',
goto_slide: 'annex-architecture',
priority: 9,
},
// === MODULE ===
{
id: 'modules-overview',
keywords: ['module', 'modules', 'baukasten', 'toolkit', '12 module', 'welche module', 'which modules', 'funktionen', 'features', 'leistungen'],
question_de: 'Welche 12 Module bietet ihr an?',
question_en: 'Which 12 modules do you offer?',
answer_de: 'Unsere Plattform besteht aus zwölf Modulen, die Kunden einzeln oder als Gesamtpaket nutzen können. Den Kern bildet das Code-Security-Modul mit SAST, DAST, SBOM-Analysen und kontinuierlichem Pentesting bei jeder Code-Änderung. Dazu kommt die CE-Software-Risikobeurteilung, die Hersteller für die CE-Kennzeichnung ihrer Produkte brauchen. Für die laufende Compliance-Dokumentation erstellen wir automatisch Verarbeitungsverzeichnisse, technisch-organisatorische Maßnahmen, Datenschutz-Folgenabschätzungen und Löschfristen. Der Audit Manager verwaltet Haupt- und Nebenabweichungen nach Audits vollständig End-to-End mit Stichtagen, Tickets und Eskalation. Darüber hinaus bieten wir Module für Betroffenenrechte, Einwilligungsmanagement, Notfallpläne bei Datenschutzvorfällen und einen Cookie-Generator. Das Compliance LLM ist ein eigenes Sprachmodell für Text und Audio, das sicher in der EU gehostet wird. Die Academy bietet Online-Schulungen für Geschäftsführung und Mitarbeiter. Abgerundet wird das Ganze durch die Integration in bestehende Kundenprozesse wie Jira und eine sichere Kommunikationslösung mit Chat, Video und einem KI-Assistenten für automatische Besprechungsnotizen.',
answer_en: 'Our platform consists of twelve modules that customers can use individually or as a complete package. The core is the code security module with SAST, DAST, SBOM analysis and continuous pentesting on every code change. Then there is the CE software risk assessment that manufacturers need for CE marking their products. For ongoing compliance documentation, we automatically generate records of processing activities, technical and organizational measures, data protection impact assessments and retention schedules. The audit manager handles major and minor deviations after audits completely end-to-end with deadlines, tickets and escalation. Beyond that, we offer modules for data subject rights, consent management, incident response for data breaches and a cookie generator. The compliance LLM is a dedicated language model for text and audio, securely hosted in the EU. The academy provides online training for management and employees. Everything is rounded off by integration into existing customer processes like Jira and a secure communication solution with chat, video and an AI assistant for automatic meeting notes.',
answer_de: 'Unsere Plattform besteht aus zwölf Modulen, die Kunden einzeln oder als Gesamtpaket nutzen können. Den Kern bildet das Code-Security-Modul mit SAST, DAST, SBOM-Analysen und kontinuierlichem Pentesting bei jeder Code-Änderung. Dazu kommt die CE-Software-Risikobeurteilung, die Hersteller für die CE-Kennzeichnung ihrer Produkte brauchen. Für die laufende Compliance-Dokumentation erstellen wir automatisch Verarbeitungsverzeichnisse, technisch-organisatorische Maßnahmen, Datenschutz-Folgenabschätzungen und Löschfristen. Der Audit Manager verwaltet Haupt- und Nebenabweichungen nach Audits vollständig End-to-End mit Stichtagen, Tickets und Eskalation. Darüber hinaus bieten wir Module für Betroffenenrechte, Einwilligungsmanagement, Notfallpläne bei Datenschutzvorfällen und einen Cookie-Generator. Das Compliance LLM ist ein eigenes Sprachmodell für Text und Audio, das sicher in der EU gehostet wird. Die Academy bietet Online-Schulungen für Geschäftsführung und Mitarbeiter. Abgerundet wird das Ganze durch die Integration in bestehende Kundenprozesse wie Jira, GitLab, Linear oder Gitea und eine sichere Kommunikationslösung mit Chat, Video und einem KI-Assistenten für automatische Besprechungsnotizen.',
answer_en: 'Our platform consists of twelve modules that customers can use individually or as a complete package. The core is the code security module with SAST, DAST, SBOM analysis and continuous pentesting on every code change. Then there is the CE software risk assessment that manufacturers need for CE marking their products. For ongoing compliance documentation, we automatically generate records of processing activities, technical and organizational measures, data protection impact assessments and retention schedules. The audit manager handles major and minor deviations after audits completely end-to-end with deadlines, tickets and escalation. Beyond that, we offer modules for data subject rights, consent management, incident response for data breaches and a cookie generator. The compliance LLM is a dedicated language model for text and audio, securely hosted in the EU. The academy provides online training for management and employees. Everything is rounded off by integration into existing customer processes like Jira, GitLab, Linear or Gitea and a secure communication solution with chat, video and an AI assistant for automatic meeting notes.',
goto_slide: 'product',
priority: 9,
},
// === TECHNOLOGIE-GLOSSAR (fuer Investor-Verstaendnis) ===
{
id: 'tech-bge-m3',
keywords: ['bge-m3', 'bge', 'embedding', 'embeddings', 'vektorisierung', 'vectorization', 'sentence transformer'],
question_de: 'Was ist BGE-M3?',
question_en: 'What is BGE-M3?',
answer_de: 'BGE-M3 ist ein State-of-the-Art Embedding-Modell, entwickelt vom Beijing Academy of Artificial Intelligence. M3 steht fuer Multi-Lingual, Multi-Functionality und Multi-Granularity. Wir nutzen es, um Gesetzestexte, Leitlinien und Compliance-Dokumente in hochdimensionale Vektoren umzuwandeln. Der Vorteil: BGE-M3 versteht ueber 100 Sprachen gleichzeitig — perfekt fuer EU-Regularien, die in verschiedenen Sprachen vorliegen. Es unterstuetzt Dense Retrieval, Sparse Retrieval und Multi-Vector Retrieval in einem Modell, was unsere Hybrid Search ermoeglicht. Das Modell laeuft lokal auf unserer EU-Infrastruktur — keine Daten verlassen den europaeischen Raum.',
answer_en: 'BGE-M3 is a state-of-the-art embedding model developed by the Beijing Academy of Artificial Intelligence. M3 stands for Multi-Lingual, Multi-Functionality and Multi-Granularity. We use it to convert legal texts, guidelines and compliance documents into high-dimensional vectors. The advantage: BGE-M3 understands over 100 languages simultaneously — perfect for EU regulations that exist in different languages. It supports dense retrieval, sparse retrieval and multi-vector retrieval in a single model, enabling our hybrid search. The model runs locally on our EU infrastructure — no data leaves the European space.',
goto_slide: 'annex-aipipeline',
priority: 7,
},
{
id: 'tech-rag',
keywords: ['rag', 'retrieval', 'augmented', 'generation', 'wissensbasis', 'knowledge base', 'wie funktioniert ki', 'how does ai work'],
question_de: 'Was ist RAG und wie nutzt ihr es?',
question_en: 'What is RAG and how do you use it?',
answer_de: 'RAG steht fuer Retrieval Augmented Generation — ein Verfahren, bei dem das KI-Modell nicht aus seinem Training antwortet, sondern zuerst in unserer Wissensbasis nach relevanten Dokumenten sucht und diese als Kontext nutzt. Das ist entscheidend fuer Compliance: Wir wollen keine halluzinierten Antworten, sondern praezise Aussagen mit Quellenangabe. Unsere RAG-Pipeline indexiert ueber 380 Gesetze, Regularien und rechtliche Dokumente in sechs Qdrant-Collections. Bei einer Anfrage werden die relevantesten Textpassagen per Hybrid Search gefunden, durch einen Cross-Encoder re-rankt und dann dem LLM als Kontext uebergeben. Das Ergebnis: jede Antwort ist quellenbasiert und nachpruefbar.',
answer_en: 'RAG stands for Retrieval Augmented Generation — a method where the AI model does not answer from its training but first searches our knowledge base for relevant documents and uses them as context. This is critical for compliance: we want no hallucinated answers but precise statements with source references. Our RAG pipeline indexes over 380 laws, regulations and legal documents in six Qdrant collections. For a query, the most relevant text passages are found via hybrid search, re-ranked by a cross-encoder and then provided to the LLM as context. The result: every answer is source-based and verifiable.',
goto_slide: 'annex-aipipeline',
priority: 8,
},
{
id: 'tech-qdrant',
keywords: ['qdrant', 'vektordatenbank', 'vector database', 'vector db', 'collections', 'similarity search'],
question_de: 'Was ist Qdrant?',
question_en: 'What is Qdrant?',
answer_de: 'Qdrant ist eine hochperformante Open-Source-Vektordatenbank, die wir fuer unsere semantische Suche nutzen. Sie speichert die Embedding-Vektoren unserer ueber 380 indexierten Regularien und ermoeglicht Similarity Search in Millisekunden. Wir betreiben sechs separate Qdrant-Collections — getrennt nach Rechtsgebiet und Dokumenttyp — fuer praezise und schnelle Ergebnisse. Qdrant laeuft auf unserer eigenen Hetzner-Infrastruktur in Deutschland, ist MIT-lizenziert und benoetigt keine Cloud-Anbindung an US-Provider.',
answer_en: 'Qdrant is a high-performance open-source vector database that we use for our semantic search. It stores the embedding vectors of our over 380 indexed regulations and enables similarity search in milliseconds. We operate six separate Qdrant collections — separated by legal domain and document type — for precise and fast results. Qdrant runs on our own Hetzner infrastructure in Germany, is MIT-licensed and requires no cloud connection to US providers.',
goto_slide: 'annex-aipipeline',
priority: 7,
},
{
id: 'tech-cross-encoder',
keywords: ['cross-encoder', 'cross encoder', 're-ranking', 'reranking', 'rerank', 'relevanz'],
question_de: 'Was ist ein Cross-Encoder?',
question_en: 'What is a cross-encoder?',
answer_de: 'Ein Cross-Encoder ist ein KI-Modell, das die Relevanz zwischen einer Suchanfrage und einem Dokument praezise bewertet. In unserer Pipeline nutzen wir ihn als zweite Stufe: Zuerst findet die schnelle Hybrid Search die Top-Kandidaten, dann bewertet der Cross-Encoder jedes Ergebnis einzeln und sortiert sie nach tatsaechlicher Relevanz. Das verbessert die Qualitaet unserer Compliance-Antworten erheblich — besonders bei juristisch komplexen Fragestellungen, wo Wortaehnlichkeit allein nicht ausreicht.',
answer_en: 'A cross-encoder is an AI model that precisely evaluates the relevance between a search query and a document. In our pipeline we use it as a second stage: first, the fast hybrid search finds the top candidates, then the cross-encoder evaluates each result individually and sorts them by actual relevance. This significantly improves the quality of our compliance answers — especially for legally complex questions where word similarity alone is not sufficient.',
goto_slide: 'annex-aipipeline',
priority: 6,
},
{
id: 'tech-sast-dast',
keywords: ['sast', 'dast', 'static analysis', 'dynamic analysis', 'code scanning', 'code analyse', 'statische analyse', 'dynamische analyse', 'penetrationstest', 'pentest'],
question_de: 'Was sind SAST und DAST?',
question_en: 'What are SAST and DAST?',
answer_de: 'SAST steht fuer Static Application Security Testing — dabei wird der Quellcode analysiert, ohne ihn auszufuehren. Man findet Schwachstellen wie SQL-Injection, Cross-Site-Scripting oder unsichere Kryptografie direkt im Code. DAST steht fuer Dynamic Application Security Testing — dabei wird die laufende Anwendung von aussen getestet, aehnlich wie ein echter Angreifer. Wir fuehren beides kontinuierlich bei jeder Code-Aenderung durch, nicht nur einmal im Jahr wie bei klassischen Pentests. Das spart dem Kunden etwa 13.000 Euro jaehrlich an externen Pentest-Kosten allein im KMU-Bereich.',
answer_en: 'SAST stands for Static Application Security Testing — it analyzes source code without executing it. You find vulnerabilities like SQL injection, cross-site scripting or insecure cryptography directly in the code. DAST stands for Dynamic Application Security Testing — it tests the running application from the outside, similar to a real attacker. We run both continuously on every code change, not just once a year like traditional pentests. This saves customers about EUR 13,000 annually in external pentest costs for SMEs alone.',
goto_slide: 'solution',
priority: 8,
},
{
id: 'tech-sbom',
keywords: ['sbom', 'software bill of materials', 'stueckliste', 'abhaengigkeiten', 'dependencies', 'supply chain', 'lieferkette'],
question_de: 'Was ist eine SBOM?',
question_en: 'What is an SBOM?',
answer_de: 'SBOM steht fuer Software Bill of Materials — eine vollstaendige Stueckliste aller Software-Komponenten, Bibliotheken und Abhaengigkeiten in einem Produkt. Mit dem Cyber Resilience Act wird die SBOM fuer alle Produkte mit digitalen Elementen in der EU zur Pflicht. Unsere Plattform generiert SBOMs automatisch bei jeder Code-Aenderung, ueberwacht bekannte Schwachstellen in Abhaengigkeiten und alarmiert sofort, wenn eine neue CVE veroeffentlicht wird. Das ist fuer produzierende Unternehmen besonders relevant, weil sie ihre Software-Lieferkette lueckenlos dokumentieren muessen.',
answer_en: 'SBOM stands for Software Bill of Materials — a complete inventory of all software components, libraries and dependencies in a product. With the Cyber Resilience Act, SBOMs become mandatory for all products with digital elements in the EU. Our platform generates SBOMs automatically on every code change, monitors known vulnerabilities in dependencies and alerts immediately when a new CVE is published. This is particularly relevant for manufacturing companies because they must document their software supply chain without gaps.',
goto_slide: 'annex-engineering',
priority: 8,
},
{
id: 'tech-bsi',
keywords: ['bsi', 'bundesamt', 'sicherheit', 'informationstechnik', 'zertifizierung', 'certification', 'c5', 'cloud security'],
question_de: 'Was bedeutet BSI-zertifiziert?',
question_en: 'What does BSI-certified mean?',
answer_de: 'BSI steht fuer das Bundesamt fuer Sicherheit in der Informationstechnik — die deutsche Bundesbehoerde fuer Cybersicherheit. Eine BSI-Zertifizierung, insbesondere der C5-Standard (Cloud Computing Compliance Criteria Catalogue), bestae­tigt, dass ein Cloud-Anbieter hoechste Sicherheitsstandards einhalt. Unsere Infrastruktur laeuft auf SysEleven, einem BSI-C5-zertifizierten deutschen Cloud-Provider. Das bedeutet: Ihre Daten werden nach den strengsten europaeischen Sicherheitsstandards geschuetzt — ohne Zugriff durch US-Behoerden.',
answer_en: 'BSI stands for the Federal Office for Information Security — the German federal authority for cybersecurity. A BSI certification, particularly the C5 standard (Cloud Computing Compliance Criteria Catalogue), confirms that a cloud provider maintains the highest security standards. Our infrastructure runs on SysEleven, a BSI C5-certified German cloud provider. This means: your data is protected according to the strictest European security standards — without access by US authorities.',
goto_slide: 'annex-architecture',
priority: 8,
},
{
id: 'tech-cloud-providers',
keywords: ['syseleven', 'hetzner', 'cloud', 'hosting', 'infrastruktur', 'infrastructure', 'server', 'rechenzentrum', 'data center', 'wo laufen', 'where hosted'],
question_de: 'Auf welcher Infrastruktur laeuft die Plattform?',
question_en: 'What infrastructure does the platform run on?',
answer_de: 'Unsere Plattform laeuft zu 100 Prozent auf europaeischer Cloud-Infrastruktur — ohne einen einzigen US-Anbieter. Fuer LLM-Inferenz und KI-Workloads nutzen wir SysEleven, einen BSI-C5-zertifizierten deutschen Cloud-Provider mit GPU-Kapazitaet. Fuer Datenbanken, Vektorspeicher und Anwendungslogik setzen wir auf Hetzner — ebenfalls deutsch, ISO 27001-zertifiziert und deutlich kostenguenstiger als AWS oder Azure. Das CI/CD laeuft ueber Gitea Actions mit automatischem Deploy via Orca auf Hetzner. Diese Kombination gibt uns einen strukturellen Kostenvorteil bei voller EU-Datensouveraenitaet.',
answer_en: 'Our platform runs 100 percent on European cloud infrastructure — without a single US provider. For LLM inference and AI workloads we use SysEleven, a BSI C5-certified German cloud provider with GPU capacity. For databases, vector storage and application logic we rely on Hetzner — also German, ISO 27001-certified and significantly more cost-effective than AWS or Azure. CI/CD runs via Gitea Actions with automatic deploy via Orca on Hetzner. This combination gives us a structural cost advantage with full EU data sovereignty.',
goto_slide: 'annex-architecture',
priority: 8,
},
{
id: 'tech-controls',
keywords: ['controls', 'pruefaspekte', 'audit aspects', 'pruefpunkte', 'checkpoints', '25000', 'control extraction'],
question_de: 'Was sind Controls bzw. Pruefaspekte?',
question_en: 'What are controls or audit aspects?',
answer_de: 'Controls sind konkrete, pruefbare Anforderungen, die aus Gesetzen und Regularien abgeleitet werden. Zum Beispiel wird aus DSGVO Artikel 32 (Sicherheit der Verarbeitung) eine Reihe konkreter Controls wie Verschluesselungspflicht, Zugriffskontrolle und regelmaessige Sicherheitstests. Wir haben ueber 25.000 solcher Controls aus ueber 380 Gesetzen, Regularien und Leitlinien extrahiert. Jeder Control hat eine eindeutige ID, ist einer Regulierung zugeordnet und kann automatisch gegen den Ist-Zustand eines Unternehmens geprueft werden. Das ist das Herzstueck unserer Compliance-Automatisierung.',
answer_en: 'Controls are concrete, verifiable requirements derived from laws and regulations. For example, GDPR Article 32 (Security of Processing) generates a series of concrete controls like encryption requirements, access control and regular security testing. We have extracted over 25,000 such controls from over 380 laws, regulations and guidelines. Each control has a unique ID, is mapped to a regulation and can be automatically checked against a company current state. This is the heart of our compliance automation.',
goto_slide: 'annex-regulatory',
priority: 8,
},
{
id: 'tech-hybrid-search',
keywords: ['hybrid search', 'hybrid suche', 'dense', 'sparse', 'bm25', 'semantic search', 'semantische suche', 'volltextsuche'],
question_de: 'Was ist Hybrid Search?',
question_en: 'What is hybrid search?',
answer_de: 'Hybrid Search kombiniert zwei Suchverfahren: Dense Retrieval (semantische Aehnlichkeit ueber Vektoren) und Sparse Retrieval (klassische Schlagwortsuche aehnlich Google). Warum beide? Juristische Texte enthalten oft spezifische Begriffe wie Artikelnummern oder Regulierungsbezeichnungen, die semantische Suche allein nicht praezise findet. Umgekehrt versteht die semantische Suche den Kontext besser als reine Schlagwortsuche. Durch die Kombination beider Verfahren mit anschliessendem Cross-Encoder Re-Ranking erreichen wir die hoechste Praezision bei Compliance-Anfragen.',
answer_en: 'Hybrid search combines two search methods: dense retrieval (semantic similarity via vectors) and sparse retrieval (classic keyword search similar to Google). Why both? Legal texts often contain specific terms like article numbers or regulation designations that semantic search alone cannot find precisely. Conversely, semantic search understands context better than pure keyword search. By combining both methods with subsequent cross-encoder re-ranking, we achieve the highest precision for compliance queries.',
goto_slide: 'annex-aipipeline',
priority: 7,
},
{
id: 'tech-policy-engine',
keywords: ['policy engine', 'deterministic', 'deterministisch', 'regeln', 'rules', 'eskalation', 'escalation', 'e0', 'e1', 'e2', 'e3'],
question_de: 'Was ist die deterministische Policy Engine?',
question_en: 'What is the deterministic policy engine?',
answer_de: 'Unsere Policy Engine ist das Gegenstueck zum LLM — sie arbeitet rein regelbasiert und deterministisch. 45 vordefinierte Regeln pruefen Compliance-Ergebnisse auf Vollstaendigkeit, Konsistenz und Dringlichkeit. Das LLM liefert die Analyse, aber die Policy Engine entscheidet, was passiert: Eskalationsstufe E0 bedeutet informativ, E1 erfordert Massnahmen, E2 hat Fristen und E3 geht an die Geschaeftsfuehrung. So stellen wir sicher, dass keine KI-Halluzination zu einer falschen Compliance-Entscheidung fuehrt.',
answer_en: 'Our policy engine is the counterpart to the LLM — it works purely rule-based and deterministically. 45 predefined rules check compliance results for completeness, consistency and urgency. The LLM delivers the analysis, but the policy engine decides what happens: escalation level E0 is informational, E1 requires action, E2 has deadlines and E3 goes to management. This ensures no AI hallucination leads to a wrong compliance decision.',
goto_slide: 'annex-aipipeline',
priority: 7,
},
{
id: 'tech-vvt-toms',
keywords: ['vvt', 'toms', 'dsfa', 'verarbeitungsverzeichnis', 'technisch organisatorische massnahmen', 'datenschutz-folgenabschaetzung', 'ropa', 'dpia', 'loeschfristen', 'retention'],
question_de: 'Was sind VVT, TOMs und DSFA?',
question_en: 'What are RoPA, TOMs and DPIA?',
answer_de: 'Das sind drei zentrale DSGVO-Dokumente, die jedes Unternehmen fuehren muss. VVT ist das Verarbeitungsverzeichnis (Record of Processing Activities) — eine Liste aller Datenverarbeitungstaetigkeiten mit Zweck, Rechtsgrundlage und Empfaengern. TOMs sind Technisch-Organisatorische Massnahmen — konkrete Sicherheitsmassnahmen wie Verschluesselung, Zugriffskontrolle oder Pseudonymisierung. DSFA ist die Datenschutz-Folgenabschaetzung (Data Protection Impact Assessment) — eine vertiefte Risikoanalyse fuer besonders sensible Verarbeitungen. Unsere Plattform generiert alle drei Dokumente automatisch und haelt sie bei Aenderungen aktuell.',
answer_en: 'These are three central GDPR documents that every company must maintain. RoPA is the Record of Processing Activities — a list of all data processing activities with purpose, legal basis and recipients. TOMs are Technical and Organizational Measures — concrete security measures like encryption, access control or pseudonymization. DPIA is the Data Protection Impact Assessment — an in-depth risk analysis for particularly sensitive processing. Our platform generates all three documents automatically and keeps them current when changes occur.',
goto_slide: 'solution',
priority: 8,
},
// === ENGINEERING DEEP DIVE — Technical Terms ===
{
id: 'eng-orca',
keywords: ['orca', 'orchestrator', 'deployment', 'deploy', 'rust'],
question_de: 'Was ist orca?',
question_en: 'What is orca?',
answer_de: 'Orca ist unser Deployment-Orchestrator — ein einzelnes Programm geschrieben in Rust, das unsere gesamte Server-Infrastruktur verwaltet. Es empfaengt automatisch Benachrichtigungen wenn neuer Code gepusht wird, baut die Software und verteilt sie auf unsere Server. Vergleichbar mit einem automatischen Hausmeister, der immer dafuer sorgt, dass die neueste Version laeuft.',
answer_en: 'Orca is our deployment orchestrator — a single binary written in Rust that manages our entire server infrastructure. It automatically receives notifications when new code is pushed, builds the software and distributes it to our servers. Think of it as an automatic caretaker ensuring the latest version is always running.',
goto_slide: 'annex-engineering',
priority: 5,
},
{
id: 'eng-infisical',
keywords: ['infisical', 'secrets', 'geheimnisse', 'passwoerter', 'passwords', 'vault', 'secret management'],
question_de: 'Was ist Infisical?',
question_en: 'What is Infisical?',
answer_de: 'Infisical ist unsere Geheimnis-Verwaltung — eine sichere Plattform, die alle Passwoerter, API-Schluessel und Zugangsdaten zentral und verschluesselt speichert. Kein Entwickler hat direkten Zugriff auf Produktions-Passwoerter. Die Software holt sich die Zugangsdaten automatisch und sicher. Das ist wie ein digitaler Tresor mit strengen Zugriffsregeln.',
answer_en: 'Infisical is our secrets management platform — it stores all passwords, API keys and credentials centrally and encrypted. No developer has direct access to production passwords. The software retrieves credentials automatically and securely. Think of it as a digital vault with strict access rules.',
goto_slide: 'annex-engineering',
priority: 5,
},
{
id: 'eng-litellm',
keywords: ['litellm', 'lite llm', 'proxy', 'gateway', 'llm proxy', 'llm gateway', 'router'],
question_de: 'Was ist LiteLLM Proxy?',
question_en: 'What is LiteLLM Proxy?',
answer_de: 'LiteLLM ist unser KI-Gateway — eine Vermittlungsschicht, die alle Anfragen an verschiedene KI-Modelle (z.B. lokale Modelle oder Claude) zentral steuert. Dadurch koennen wir jederzeit das KI-Modell wechseln, Kosten kontrollieren und sicherstellen, dass keine sensiblen Daten unkontrolliert an externe Anbieter fliessen. Es ist wie eine Telefonzentrale fuer KI-Anfragen.',
answer_en: 'LiteLLM is our AI gateway — a routing layer that centrally manages all requests to different AI models (e.g. local models or Claude). This lets us switch AI models anytime, control costs, and ensure no sensitive data flows uncontrolled to external providers. Think of it as a switchboard for AI requests.',
goto_slide: 'annex-architecture',
priority: 6,
},
{
id: 'eng-guardrails',
keywords: ['guardrails', 'leitplanken', 'sicherheit', 'ki sicherheit', 'ai safety', 'halluzination'],
question_de: 'Was sind Guardrails?',
question_en: 'What are Guardrails?',
answer_de: 'Guardrails sind Sicherheitsleitplanken fuer unsere KI — automatische Pruefungen, die verhindern, dass die KI falsche, gefaehrliche oder nicht-konforme Antworten gibt. Jede KI-Antwort wird gegen definierte Regeln geprueft, bevor sie an den Nutzer geht. Beispiel: Die KI darf keine Rechtsberatung geben, sondern nur auf Gesetzestexte verweisen.',
answer_en: 'Guardrails are safety rails for our AI — automatic checks that prevent the AI from giving wrong, dangerous or non-compliant answers. Every AI response is validated against defined rules before reaching the user. Example: The AI must not give legal advice, only reference legal texts.',
goto_slide: 'annex-architecture',
priority: 6,
},
{
id: 'eng-pii-redaction',
keywords: ['pii', 'redaction', 'redaktion', 'personenbezogen', 'anonymisierung', 'datenschutz gateway'],
question_de: 'Was ist PII Redaction?',
question_en: 'What is PII Redaction?',
answer_de: 'PII steht fuer Personally Identifiable Information (personenbezogene Daten). Unser PII-Redaction-Gateway entfernt automatisch alle personenbezogenen Daten (Namen, E-Mails, Adressen) aus Texten, BEVOR sie an ein KI-Modell gesendet werden. So verlassen keine Kundendaten jemals unsere EU-Infrastruktur — selbst wenn wir externe KI-Modelle nutzen.',
answer_en: 'PII stands for Personally Identifiable Information. Our PII Redaction Gateway automatically removes all personal data (names, emails, addresses) from texts BEFORE they are sent to an AI model. This way, no customer data ever leaves our EU infrastructure — even when using external AI models.',
goto_slide: 'annex-architecture',
priority: 7,
},
{
id: 'eng-llm-inferenz',
keywords: ['inferenz', 'inference', 'llm inferenz', 'ki berechnung', 'ai computation'],
question_de: 'Was ist LLM-Inferenz?',
question_en: 'What is LLM inference?',
answer_de: 'Inferenz bedeutet, dass ein KI-Modell eine Anfrage bearbeitet und eine Antwort generiert — also der eigentliche Denkprozess der KI. Wenn ein Nutzer eine Compliance-Frage stellt, fuehrt unser System eine Inferenz durch: Das Modell analysiert die Frage, durchsucht relevante Gesetze und formuliert eine fundierte Antwort.',
answer_en: 'Inference means an AI model processes a request and generates a response — the actual thinking process of the AI. When a user asks a compliance question, our system runs an inference: the model analyzes the question, searches relevant laws and formulates a well-founded answer.',
goto_slide: 'annex-architecture',
priority: 5,
},
{
id: 'eng-embedding',
keywords: ['embedding', 'einbettung', 'vektoren', 'vectors', 'embedding service'],
question_de: 'Was ist ein Embedding Service?',
question_en: 'What is an Embedding Service?',
answer_de: 'Ein Embedding Service wandelt Texte in mathematische Vektoren (Zahlenlisten) um, die ihre Bedeutung repraesentieren. Dadurch kann unsere Plattform aehnliche Inhalte finden — z.B. welcher Gesetzesparagraph zu einer bestimmten Compliance-Frage passt. Es ist wie ein intelligentes Inhaltsverzeichnis, das nicht nach Woertern, sondern nach Bedeutung sucht.',
answer_en: 'An Embedding Service converts texts into mathematical vectors (lists of numbers) that represent their meaning. This lets our platform find similar content — e.g. which law paragraph matches a specific compliance question. Think of it as an intelligent table of contents that searches by meaning, not by words.',
goto_slide: 'annex-architecture',
priority: 5,
},
{
id: 'eng-vector-db',
keywords: ['vektor', 'vector', 'datenbank', 'database', 'qdrant', 'vektordatenbank'],
question_de: 'Was ist eine Vektordatenbank / Qdrant?',
question_en: 'What is a vector database / Qdrant?',
answer_de: 'Qdrant ist unsere Vektordatenbank — eine spezielle Datenbank, die nicht nach exakten Woertern sucht, sondern nach inhaltlicher Aehnlichkeit. Dort sind alle 380+ Gesetze und Regularien als Vektoren gespeichert. Wenn ein Kunde eine Compliance-Frage stellt, findet Qdrant in Millisekunden die relevantesten Paragraphen — auch wenn die Frage ganz anders formuliert ist als der Gesetzestext.',
answer_en: 'Qdrant is our vector database — a specialized database that searches not by exact words but by semantic similarity. All 380+ laws and regulations are stored there as vectors. When a customer asks a compliance question, Qdrant finds the most relevant paragraphs in milliseconds — even if the question is worded completely differently from the legal text.',
goto_slide: 'annex-architecture',
priority: 6,
},
{
id: 'eng-gitea',
keywords: ['gitea', 'git', 'versionskontrolle', 'version control', 'ci', 'cd', 'pipeline', 'actions'],
question_de: 'Was ist Gitea + Actions?',
question_en: 'What is Gitea + Actions?',
answer_de: 'Gitea ist unser selbst gehosteter Git-Server — vergleichbar mit GitHub, aber auf unseren eigenen EU-Servern. Actions ist das dazugehoerige CI/CD-System: Bei jeder Code-Aenderung laufen automatisch Tests, Sicherheitschecks und Deployments. Wir sind dadurch unabhaengig von US-Anbietern wie GitHub und behalten volle Kontrolle ueber unseren Quellcode.',
answer_en: 'Gitea is our self-hosted Git server — comparable to GitHub but on our own EU servers. Actions is the attached CI/CD system: with every code change, tests, security checks and deployments run automatically. This makes us independent from US providers like GitHub and gives us full control over our source code.',
goto_slide: 'annex-engineering',
priority: 5,
},
{
id: 'eng-registry',
keywords: ['registry', 'container', 'docker', 'image', 'private registry'],
question_de: 'Was ist eine Private Registry?',
question_en: 'What is a Private Registry?',
answer_de: 'Eine Private Registry ist unser eigener App Store fuer Software-Pakete. Jede Version unserer Software wird als Container-Image gebaut und signiert in unserer eigenen Registry gespeichert — auf EU-Servern, nicht bei Docker Hub oder Amazon. So stellen wir sicher, dass nur gepruefter Code in die Produktion gelangt.',
answer_en: 'A Private Registry is our own app store for software packages. Every version of our software is built as a container image and stored signed in our own registry — on EU servers, not on Docker Hub or Amazon. This ensures only verified code reaches production.',
goto_slide: 'annex-engineering',
priority: 4,
},
{
id: 'eng-trivy-semgrep',
keywords: ['trivy', 'semgrep', 'scanner', 'vulnerability', 'schwachstelle', 'container security'],
question_de: 'Was sind Trivy und Semgrep?',
question_en: 'What are Trivy and Semgrep?',
answer_de: 'Trivy und Semgrep sind automatische Sicherheitsscanner. Trivy prueft unsere Container-Images auf bekannte Schwachstellen — wie ein TÜV fuer Software-Pakete. Semgrep analysiert unseren Quellcode auf Sicherheitsluecken und schlechte Muster. Beide laufen bei jeder Code-Aenderung automatisch — wir nutzen dieselben Tools, die wir unseren Kunden anbieten.',
answer_en: 'Trivy and Semgrep are automatic security scanners. Trivy checks our container images for known vulnerabilities — like a safety inspection for software packages. Semgrep analyzes our source code for security holes and bad patterns. Both run automatically with every code change — we use the same tools we offer our customers.',
goto_slide: 'annex-engineering',
priority: 5,
},
{
id: 'eng-gitleaks',
keywords: ['gitleaks', 'secret detection', 'geheimnis erkennung', 'passwort leak'],
question_de: 'Was ist Gitleaks / Secret Detection?',
question_en: 'What is Gitleaks / Secret Detection?',
answer_de: 'Gitleaks ist ein Tool, das automatisch verhindert, dass Entwickler versehentlich Passwoerter oder API-Schluessel im Quellcode veroeffentlichen. Bei jedem Code-Commit wird geprueft, ob sensible Daten enthalten sind — wenn ja, wird der Commit blockiert. Das ist eine der haeufigsten Ursachen fuer Datenlecks und wir schuetzen sowohl uns selbst als auch unsere Kunden davor.',
answer_en: 'Gitleaks is a tool that automatically prevents developers from accidentally publishing passwords or API keys in source code. Every code commit is checked for sensitive data — if found, the commit is blocked. This is one of the most common causes of data leaks and we protect both ourselves and our customers from it.',
goto_slide: 'annex-engineering',
priority: 5,
},
{
id: 'eng-devsecops',
keywords: ['devsecops', 'dev sec ops', 'sicherheit entwicklung', 'security development'],
question_de: 'Was bedeutet DevSecOps?',
question_en: 'What does DevSecOps mean?',
answer_de: 'DevSecOps steht fuer Development + Security + Operations — die Philosophie, dass Sicherheit kein nachtraeglicher Schritt ist, sondern in jeden Entwicklungsschritt integriert wird. Bei uns laufen Sicherheitschecks (SAST, DAST, SBOM, Secret Detection) bei JEDER Code-Aenderung automatisch. Sicherheitsluecken werden sofort erkannt, nicht erst beim jaehrlichen Audit.',
answer_en: 'DevSecOps stands for Development + Security + Operations — the philosophy that security is not an afterthought but integrated into every development step. With us, security checks (SAST, DAST, SBOM, secret detection) run automatically with EVERY code change. Vulnerabilities are caught immediately, not at the annual audit.',
goto_slide: 'annex-engineering',
priority: 6,
},
{
id: 'eng-syseleven-bsi',
keywords: ['syseleven', 'bsi', 'c5', 'cloud zertifizierung', 'cloud certification', 'hetzner', 'eu cloud'],
question_de: 'Was bedeutet SysEleven / BSI C5?',
question_en: 'What does SysEleven / BSI C5 mean?',
answer_de: 'SysEleven ist ein deutscher Cloud-Anbieter mit BSI-C5-Zertifizierung. BSI C5 ist der Sicherheitsstandard des Bundesamts fuer Sicherheit in der Informationstechnik fuer Cloud-Dienste — quasi der TÜV fuer Cloud-Anbieter. Hetzner ist ein weiterer deutscher Cloud-Anbieter, den wir nutzen. Alle unsere Daten liegen ausschliesslich auf deutschen/EU-Servern — kein US-Anbieter hat Zugriff.',
answer_en: 'SysEleven is a German cloud provider with BSI C5 certification. BSI C5 is the security standard of the German Federal Office for Information Security for cloud services — essentially the TÜV for cloud providers. Hetzner is another German cloud provider we use. All our data resides exclusively on German/EU servers — no US provider has access.',
goto_slide: 'annex-engineering',
priority: 6,
},
{
id: 'eng-microservices',
keywords: ['microservice', 'architektur', 'architecture', 'container', 'services', 'modular'],
question_de: 'Was bedeutet Microservice-Architektur?',
question_en: 'What is microservice architecture?',
answer_de: 'Microservices bedeutet, dass unsere Plattform nicht ein grosses Programm ist, sondern aus vielen kleinen, unabhaengigen Diensten besteht. Jeder Dienst hat eine klare Aufgabe (z.B. Authentifizierung, RAG-Suche, Code-Scanning). Vorteil: Einzelne Teile koennen unabhaengig aktualisiert, skaliert und repariert werden, ohne das Gesamtsystem zu beeintraechtigen.',
answer_en: 'Microservices means our platform is not one large program but consists of many small, independent services. Each service has a clear responsibility (e.g. authentication, RAG search, code scanning). Advantage: individual parts can be updated, scaled and repaired independently without affecting the overall system.',
goto_slide: 'annex-engineering',
priority: 5,
},
{
id: 'eng-ipfs',
keywords: ['ipfs', 'dsms', 'blockchain', 'dezentral', 'decentralized', 'archivierung', 'archiving'],
question_de: 'Was ist IPFS/DSMS?',
question_en: 'What is IPFS/DSMS?',
answer_de: 'IPFS (InterPlanetary File System) ist ein dezentrales Speichersystem — Dateien werden nicht an einem Ort gespeichert, sondern verteilt und kryptographisch gesichert. Wir nutzen das optional fuer die revisionssichere Archivierung von Compliance-Dokumenten: Einmal gespeicherte Nachweise koennen nachtraeglich nicht mehr veraendert werden. Das ist wie ein digitaler Notar.',
answer_en: 'IPFS (InterPlanetary File System) is a decentralized storage system — files are not stored in one place but distributed and cryptographically secured. We optionally use this for tamper-proof archiving of compliance documents: once stored, evidence cannot be modified afterwards. It is like a digital notary.',
goto_slide: 'annex-architecture',
priority: 4,
},
{
id: 'eng-fastapi-go-nextjs',
keywords: ['fastapi', 'go', 'gin', 'nextjs', 'next.js', 'typescript', 'python', 'programmiersprache', 'programming language', 'tech stack'],
question_de: 'Warum nutzt ihr drei Programmiersprachen?',
question_en: 'Why do you use three programming languages?',
answer_de: 'Jede Sprache hat ihre Staerke: Python (FastAPI) fuer KI und Datenverarbeitung — das gesamte Machine-Learning-Oekosystem ist in Python. Go (Gin) fuer hochperformante API-Dienste mit minimaler Latenz und Ressourcenverbrauch. TypeScript (Next.js) fuer moderne Web-Oberflaechen. Das ist Industriestandard — auch Unternehmen wie Google, Uber und Stripe nutzen mehrere Sprachen je nach Aufgabe.',
answer_en: 'Each language has its strength: Python (FastAPI) for AI and data processing — the entire ML ecosystem is in Python. Go (Gin) for high-performance API services with minimal latency. TypeScript (Next.js) for modern web interfaces. This is industry standard — companies like Google, Uber and Stripe also use multiple languages per task.',
goto_slide: 'annex-engineering',
priority: 5,
},
{
id: 'eng-iac-scanning',
keywords: ['iac', 'infrastructure as code', 'infrastruktur', 'konfiguration', 'configuration'],
question_de: 'Was ist IaC Scanning?',
question_en: 'What is IaC Scanning?',
answer_de: 'IaC steht fuer Infrastructure as Code — die Serverkonfiguration wird als Code geschrieben statt manuell eingerichtet. IaC Scanning prueft diese Konfigurationsdateien automatisch auf Sicherheitsluecken, z.B. offene Ports, fehlende Verschluesselung oder zu breite Zugriffsrechte. So werden Infrastruktur-Fehler erkannt, bevor sie in Produktion gehen.',
answer_en: 'IaC stands for Infrastructure as Code — server configuration is written as code instead of manually configured. IaC Scanning automatically checks these configuration files for security issues like open ports, missing encryption or overly broad access rights. This catches infrastructure errors before they go to production.',
goto_slide: 'annex-engineering',
priority: 4,
},
{
id: 'eng-postgresql',
keywords: ['postgresql', 'postgres', 'datenbank', 'database', 'sql', 'relational'],
question_de: 'Was ist PostgreSQL?',
question_en: 'What is PostgreSQL?',
answer_de: 'PostgreSQL ist die weltweit fuehrende Open-Source-Datenbank — vergleichbar mit Oracle oder Microsoft SQL Server, aber kostenlos und ohne Lizenzgebuehren. Sie speichert alle strukturierten Daten unserer Plattform: Kunden, Compliance-Dokumente, Audit-Trails, Nutzerkonten. PostgreSQL wird von Unternehmen wie Apple, Instagram und Spotify eingesetzt.',
answer_en: 'PostgreSQL is the world-leading open-source database — comparable to Oracle or Microsoft SQL Server but free and without license fees. It stores all structured data of our platform: customers, compliance documents, audit trails, user accounts. PostgreSQL is used by companies like Apple, Instagram and Spotify.',
goto_slide: 'annex-engineering',
priority: 4,
},
{
id: 'eng-cyclonedx-sbom',
keywords: ['cyclonedx', 'spdx', 'sbom', 'bill of materials', 'stueckliste', 'abhaengigkeiten', 'dependencies'],
question_de: 'Was ist CycloneDX SBOM?',
question_en: 'What is CycloneDX SBOM?',
answer_de: 'SBOM steht fuer Software Bill of Materials — eine vollstaendige Stueckliste aller Software-Komponenten, die in einem Produkt enthalten sind. CycloneDX ist das Standardformat dafuer. Das ist wie ein Zutatenverzeichnis bei Lebensmitteln: Der Kunde sieht genau, welche Bibliotheken und Versionen verbaut sind. Ab 2025 durch den EU Cyber Resilience Act fuer viele Produkte Pflicht.',
answer_en: 'SBOM stands for Software Bill of Materials — a complete list of all software components contained in a product. CycloneDX is the standard format for this. Think of it like an ingredient list for food: the customer sees exactly which libraries and versions are included. Required for many products from 2025 under the EU Cyber Resilience Act.',
goto_slide: 'annex-engineering',
priority: 5,
},
]
+284 -212
View File
@@ -7,18 +7,18 @@ export const PRESENTER_SCRIPT: SlideScript[] = [
duration: 45,
paragraphs: [
{
text_de: 'Willkommen bei BreakPilot COMPLAI — die Cloud-SDK-Plattform für vollständige Compliance: von der DSGVO über den AI Act bis zur CE-Kennzeichnung.',
text_en: 'Welcome to BreakPilot COMPLAI — the cloud SDK platform for complete compliance: from GDPR to the AI Act to CE certification.',
text_de: 'Willkommen bei BreakPilot COMPLAI — der Compliance-Plattform, die Regulierungsanforderungen für produzierende Unternehmen automatisiert. Von der Datenschutz-Grundverordnung über den AI Act bis zur C. E. Kennzeichnung — wir heben extreme Einsparpotentiale in Rechtsberatung, Datenschutz und Auditvorbereitung.',
text_en: 'Welcome to BreakPilot COMPLAI — the compliance platform that automates regulatory requirements for manufacturing companies. From GDPR to the AI Act to CE certification — we unlock massive savings in legal consulting, data protection and audit preparation.',
pause_after: 1500,
},
{
text_de: 'Ich bin Ihr KI-Präsentator und führe Sie durch unser Pitch Deck. Die Präsentation dauert etwa 15 Minuten.',
text_en: 'I am your AI presenter and will guide you through our pitch deck. The presentation takes about 15 minutes.',
text_de: 'Ich bin Ihr KI-Präsentator und führe Sie durch unser Pitch Deck. Die Präsentation dauert etwa 15 Minuten — und ich verspreche Ihnen: Es lohnt sich.',
text_en: 'I am your AI presenter and will guide you through our pitch deck. The presentation takes about 15 minutes — and I promise: it will be worth it.',
pause_after: 1500,
},
{
text_de: 'Sie können jederzeit Fragen stellen — nutzen Sie einfach den Chat. Ich pausiere automatisch und antworte sofort.',
text_en: 'You can ask questions at any time — just use the chat. I will pause automatically and respond immediately.',
text_de: 'Sie können jederzeit Fragen stellen — nutzen Sie einfach den Chat. Ich pausiere automatisch und antworte sofort. Das ist übrigens schon ein Vorgeschmack auf unsere Technologie.',
text_en: 'You can ask questions at any time — just use the chat. I will pause automatically and respond immediately. By the way, this is already a taste of our technology.',
pause_after: 1000,
},
],
@@ -32,13 +32,13 @@ export const PRESENTER_SCRIPT: SlideScript[] = [
duration: 30,
paragraphs: [
{
text_de: 'Bevor wir ins Detail gehen, hier das Wichtigste auf einen Blick: BreakPilot COMPLAI bietet Full Compliance GPT, CE-Software-Risikobeurteilung und DevSecOps aus einer Hand — speziell für den Maschinenbau.',
text_en: 'Before we dive into details, here is the key summary: BreakPilot COMPLAI offers full compliance GPT, CE software risk assessment and DevSecOps from a single platform — specifically for machine manufacturing.',
text_de: 'Bevor wir ins Detail gehen, hier das Wichtigste auf einen Blick: BreakPilot COMPLAI ist die einzige Plattform, die organisatorische Compliance, Produkt-Compliance und Code-Security vereint — speziell für den produzierenden Mittelstand.',
text_en: 'Before we dive into details, here is the key summary: BreakPilot COMPLAI is the only platform that combines organizational compliance, product compliance and code security — specifically for the manufacturing mid-market.',
pause_after: 1500,
},
{
text_de: '12 Module: Code Security, CE-Software-Risikobeurteilung, Compliance-Dokumente, Audit Manager, DSR, Consent, Notfallpläne, Cookie-Generator, Compliance LLM, Academy, Integration und sichere Kommunikation. 110 Gesetze und Regularien, 25.000 Prüfaspekte. Sie können den Onepager als PDF herunterladen.',
text_en: '12 modules: code security, CE software risk assessment, compliance documents, audit manager, DSR, consent, incident response, cookie generator, compliance LLM, academy, integration and secure communication. 110 laws and regulations, 25,000 audit aspects. You can download the one-pager as PDF.',
text_de: 'Über 380 Gesetze, Regularien und rechtliche Dokumente in unserer KI-Wissensbasis. Über 25.000 extrahierte Prüfaspekte. 12 Produkt-Module. Und das Beste: Unsere Kunden sparen ab Tag eins mehr als sie zahlen. Die Executive Summary können Sie als PDF herunterladen.',
text_en: 'Over 380 laws, regulations and legal documents in our AI knowledge base. Over 25,000 extracted audit aspects. 12 product modules. And the best part: our customers save more than they pay from day one. You can download the executive summary as PDF.',
pause_after: 1000,
},
],
@@ -52,8 +52,8 @@ export const PRESENTER_SCRIPT: SlideScript[] = [
duration: 20,
paragraphs: [
{
text_de: 'BreakPilot COMPLAI — 12 Module, 110 Gesetze, 10 Branchen, eine Plattform. Kontinuierliche Compliance und Code-Security. Gründung Juli/August 2026.',
text_en: 'BreakPilot COMPLAI — 12 modules, 110 laws, 10 industries, one platform. Continuous compliance and code security. Founding July/August 2026.',
text_de: 'BreakPilot COMPLAI — über 380 Regularien, 10 Branchen, eine Plattform. Kontinuierliche Compliance und Code-Security. Gründung August 2026. Wir bauen die Zukunft der industriellen Compliance.',
text_en: 'BreakPilot COMPLAI — over 380 regulations, 10 industries, one platform. Continuous compliance and code security. Founding August 2026. We are building the future of industrial compliance.',
pause_after: 1500,
},
],
@@ -61,54 +61,79 @@ export const PRESENTER_SCRIPT: SlideScript[] = [
transition_hint_en: 'Let us first look at the problem.',
},
// 2 — problem (60s)
// 3 — problem (60s)
{
slideId: 'problem',
duration: 60,
paragraphs: [
{
text_de: 'Maschinenbauer — oft KMU mit 10 bis 500 Mitarbeitern — wollen KI nutzen. Aber sie weigern sich, Microsoft Copilot oder Claude auf ihr Herzstück loszulassen. Die Angst vor Datenmissbrauch durch US-Konzerne ist real und berechtigt.',
text_en: 'Machine manufacturers — often SMEs with 10 to 500 employees want to use AI. But they refuse to let Microsoft Copilot or Claude touch their core IP. The fear of data misuse by US corporations is real and justified.',
text_de: 'Stellen Sie sich vor: Ein Maschinenbauer mit 100 Mitarbeitern will KI in seinen Produktionsprozess integrieren. Aber er traut sich nicht. Die Angst vor Datenmissbrauch durch US-Konzerne ist real und berechtigt. Der Patriots Act macht europäische Daten auf US-Servern angreifbar.',
text_en: 'Imagine: A machine builder with 100 employees wants to integrate AI into their production process. But they do not dare. The fear of data misuse by US corporations is real and justified. The Patriot Act makes European data on US servers vulnerable.',
pause_after: 2000,
},
{
text_de: 'Wer US-SaaS meidet, bleibt von der KI-Revolution abgeschnitten. Wer alles zu AWS oder Google schiebt, akzeptiert, dass selbst europäische Server über den Patriots Act abgesaugt werden können. Deutsche KMU sitzen in der Falle.',
text_en: 'Those avoiding US SaaS are cut off from the AI revolution. Those pushing everything to AWS or Google accept that even European servers can be accessed via the Patriot Act. German SMEs are trapped.',
text_de: 'Das Ergebnis ist ein Dilemma: Wer US Software-as-a-Service meidet, bleibt von der KI-Revolution abgeschnitten. Wer es nutzt, riskiert seine Kundendaten und Geschäftsgeheimnisse. Deutsche Mittelständler sitzen in der Falle — und das in einer Zeit, in der kontinuierlich neue EU-Regulierungen auf sie einprasseln.',
text_en: 'The result is a dilemma: those avoiding US SaaS are cut off from the AI revolution. Those using it risk their customer data and trade secrets. German mid-market companies are trapped — at a time when new EU regulations are continuously hitting them.',
pause_after: 2500,
},
{
text_de: 'Gleichzeitig überrollen fünf EU-Gesetze diese Unternehmen: AI Act, NIS2, CRA, DSGVO und die Maschinenverordnung. Pentests und CE-Zertifizierungen kosten 50.000 Euro im Jahr — aber prüfen nur einmal. Nichts läuft kontinuierlich.',
text_en: 'Meanwhile, five EU laws are overwhelming these companies: AI Act, NIS2, CRA, GDPR and the Machinery Regulation. Pentests and CE certifications cost EUR 50,000 per year — but only check once. Nothing runs continuously.',
text_de: 'AI Act, NIS 2 Richtlinie, Cyber Resilience Act, Datenschutz-Grundverordnung, Maschinenverordnung — jedes einzelne Gesetz erfordert Expertise, die kein kleines und mittleres Unternehmen intern vorhalten kann. Externe Security Tests und Zertifizierungen verursachen sehr hohe Kosten — und prüfen nur einmal im Jahr. Am nächsten Tag kann alles wieder veraltet sein.',
text_en: 'AI Act, NIS2, CRA, GDPR, Machinery Regulation — each law requires expertise that no SME maintains internally. External security tests and certifications cause very high costs — and only check once a year. The next day everything could be outdated again.',
pause_after: 1500,
},
],
transition_hint_de: 'Und genau dafür haben wir eine Lösung.',
transition_hint_en: 'And that is exactly what we have a solution for.',
transition_hint_de: 'Und genau hier setzen wir an.',
transition_hint_en: 'And that is exactly where we step in.',
},
// 3 — solution (70s)
// 4 — solution (70s)
{
slideId: 'solution',
duration: 70,
paragraphs: [
{
text_de: 'Unsere Lösung: Kontinuierliche Software-Compliance statt jährlicher Stichproben. SAST, DAST, SBOM und Pentesting bei jeder Code-Änderung. Findings landen direkt als Jira-Tickets mit konkreten Implementierungsvorschlägen.',
text_en: 'Our solution: Continuous software compliance instead of annual spot checks. SAST, DAST, SBOM and pentesting on every code change. Findings land directly as Jira tickets with concrete implementation suggestions.',
text_de: 'Unsere Lösung macht Schluss mit dem jährlichen Compliance-Zirkus. Statt einmal im Jahr einen teuren Berater zu buchen, läuft bei uns alles kontinuierlich: Security Tests, Software-Stücklisten und Pentesting bei jeder Code-Änderung. Automatisch. Rund um die Uhr.',
text_en: 'Our solution ends the annual compliance circus. Instead of booking an expensive consultant once a year, everything runs continuously on our platform: security tests, software bills of materials and pentesting on every code change. Automatically. Around the clock.',
pause_after: 2000,
},
{
text_de: 'VVT, TOMs, DSFA, Löschfristen und CE-Risikobeurteilung werden automatisch generiert. Und nach dem Audit? Haupt- und Nebenabweichungen werden End-to-End abgearbeitet: Rollen zuweisen, Stichtage setzen, Tickets erstellen, Nachweise einfordern. Die Geschäftsführung bekommt Eskalationsberichte — kein Mitarbeiter muss mehr hinterherlaufen.',
text_en: 'RoPA, TOMs, DPIA, retention policies and CE risk assessment are generated automatically. And after the audit? Major and minor deviations are handled end-to-end: assign roles, set deadlines, create tickets, collect evidence. Management gets escalation reports — no employee needs to chase anyone.',
text_de: 'Aber das ist erst der Anfang. Verarbeitungsverzeichnis, technisch-organisatorische Maßnahmen, Datenschutz-Folgenabschätzung, Löschfristen, C. E. Risikobeurteilung — alles wird automatisch generiert und aktuell gehalten. Nach dem Audit werden Abweichungen End-to-End abgearbeitet: Rollen zuweisen, Stichtage setzen, Tickets erstellen, Nachweise einfordern. Kein Mitarbeiter muss mehr hinterherlaufen — die Plattform eskaliert automatisch bis zur Geschäftsführung.',
text_en: 'But that is just the beginning. RoPA, TOMs, DPIA, retention policies, CE risk assessment — everything is generated automatically and kept up to date. After the audit, deviations are handled end-to-end: assign roles, set deadlines, create tickets, collect evidence. No employee needs to chase anyone — the platform automatically escalates to management.',
pause_after: 2500,
},
{
text_de: 'Die Plattform läuft auf einer BSI-zertifizierten Cloud in Deutschland oder OVH in Frankreich. Jitsi für Video, Matrix für Chat, KI-Aufgabenerstellung aus Audiomitschnitten direkt in Kundensysteme. Keine US-SaaS im Source Code.',
text_en: 'The platform runs on a BSI-certified cloud in Germany or OVH in France. Jitsi for video, Matrix for chat, AI task creation from audio recordings directly into customer systems. No US SaaS in source code.',
pause_after: 2500,
text_de: 'Und das Wichtigste für datensensible Unternehmen: Die Plattform läuft auf einer Bundesamt für Sicherheit in der Informationstechnik-zertifizierten Cloud in Deutschland. Kein US Software-as-a-Service im gesamten Source Code. Kommunikation über Jitsi und Matrix — vollständig souverän.',
text_en: 'And the most important thing for data-sensitive companies: The platform runs on a BSI-certified cloud in Germany. No US SaaS in the entire source code. Communication via Jitsi and Matrix — completely sovereign.',
pause_after: 2000,
},
{
text_de: 'Das Ergebnis: Ein Kunde zahlt etwa 50.000 Euro im Jahr und spart 30.000 Euro für Pentests, 20.000 Euro für CE-Beurteilungen, kann Auditmanager entlasten und reagiert auf Kundenanfragen in Echtzeit.',
text_en: 'The result: A customer pays about EUR 50,000 per year and saves EUR 30,000 on pentests, EUR 20,000 on CE assessments, can relieve audit managers and responds to customer inquiries in real time.',
text_de: 'Das Ergebnis für den Kunden: Ab Tag eins spart er mehr als er zahlt. Ein kleine und mittlere Unternehmen mit 25 Mitarbeitern spart 55.000 Euro im Jahr — bei Kosten von nur 15.000 Euro. Das ist ein Return on Investment von 3,7x.',
text_en: 'The result for the customer: From day one they save more than they pay. An SME with 25 employees saves EUR 55,000 per year — at a cost of only EUR 15,000. That is a 3.7x ROI.',
pause_after: 1500,
},
],
transition_hint_de: 'Schauen wir uns die regulatorische Landschaft genauer an.',
transition_hint_en: 'Let us look at the regulatory landscape more closely.',
},
// — usp (45s)
{
slideId: 'usp',
duration: 45,
paragraphs: [
{
text_de: 'Unsere Plattform verbindet zwei Welten, die heute getrennt sind: Compliance und Code. Links sehen Sie die Compliance-Seite — RFQ-Prüfung und Prozess-Compliance. Rechts die Code-Seite — bidirektionale Synchronisation und kontinuierliche Prüfung bei jedem Commit.',
text_en: 'Our platform connects two worlds that are separate today: compliance and code. On the left you see the compliance side — RFQ verification and process compliance. On the right the code side — bidirectional synchronization and continuous checks on every commit.',
pause_after: 2000,
},
{
text_de: 'In der Mitte: Alles ist immer synchron. Compliance-Änderungen fliessen automatisch in den Code. Code-Änderungen aktualisieren automatisch die Compliance-Dokumentation. Kein Informationsverlust zwischen Audit und Entwicklung.',
text_en: 'In the center: everything is always in sync. Compliance changes flow automatically into code. Code changes automatically update compliance documentation. No information loss between audit and development.',
pause_after: 2000,
},
{
text_de: 'Unser Moat: Jeder kann sagen, was verboten ist. Kaum jemand kann sagen, wie weit man maximal gehen kann. Das ist unser Produkt — der Compliance Optimizer.',
text_en: 'Our Moat: Everyone can say what is forbidden. Almost no one can tell you how far you can actually go. That is our product — the Compliance Optimizer.',
pause_after: 1500,
},
],
@@ -122,98 +147,93 @@ export const PRESENTER_SCRIPT: SlideScript[] = [
duration: 35,
paragraphs: [
{
text_de: 'Diese Matrix zeigt, warum eine universelle Lösung nötig ist: Jede Branche hat eine einzigartige Kombination von Regularien. 110 Gesetze und Regularien über 10 Branchen — alle in unserem RAG-System indexiert.',
text_en: 'This matrix shows why a universal solution is needed: Each industry has a unique combination of regulations. 110 laws and regulations across 10 industries — all indexed in our RAG system.',
text_de: 'Diese Matrix zeigt, warum eine universelle Lösung nötig ist: Jede Branche hat eine einzigartige Kombination von Regularien. Über 380 Gesetze, Regularien und rechtliche Dokumente über 10 Branchen — alle in unserem RAG-System indexiert und mit KI durchsuchbar.',
text_en: 'This matrix shows why a universal solution is needed: Each industry has a unique combination of regulations. Over 380 laws, regulations and legal documents across 10 industries — all indexed in our RAG system and searchable with AI.',
pause_after: 2000,
},
{
text_de: 'Über 25.000 Prüfaspekte bilden die Grundlage für automatisierte Compliance-Analyse und Risikobewertung. Daraus werden Pflichten pro Branche und Regulierung abgeleitet.',
text_en: 'Over 25,000 audit aspects form the foundation for automated compliance analysis and risk assessment. Obligations per industry and regulation are derived from these.',
text_de: 'Über 25.000 extrahierte Prüfaspekte bilden das Fundament für automatisierte Compliance-Analyse. Daraus leiten wir konkrete Pflichten pro Branche ab — kein Unternehmen muss mehr selbst recherchieren, was für sie gilt.',
text_en: 'Over 25,000 extracted audit aspects form the foundation for automated compliance analysis. From these we derive concrete obligations per industry — no company needs to research what applies to them anymore.',
pause_after: 1500,
},
],
transition_hint_de: 'Nun zu unseren Produkten.',
transition_hint_en: 'Now to our products.',
transition_hint_de: 'Nun zu unserem Produkt.',
transition_hint_en: 'Now to our product.',
},
// — product (65s)
// 6 — product (40s)
{
slideId: 'product',
duration: 65,
duration: 40,
paragraphs: [
{
text_de: 'Unsere Plattform ist modular aufgebaut. 12 Module im Baukasten: Code Security, CE-Software-Risikobeurteilung, Compliance-Dokumente, Audit Manager, DSR, Consent, Notfallpläne, Cookie-Generator, LLM, Academy, Integration und sichere Kommunikation.',
text_en: 'Our platform is modular. 12 modules in the toolkit: code security, CE software risk assessment, compliance documents, audit manager, DSR, consent, incident response, cookie generator, LLM, academy, integration and secure communication.',
pause_after: 2500,
},
{
text_de: 'Das Pricing ist mitarbeiterbasiert und am Markt validiert. Unternehmen mit über 250 Mitarbeitern zahlen etwa 25.000 bis 50.000 Euro im Jahr. Dafür sparen sie 50.000 Euro und mehr — für Pentests, CE-Beurteilungen und Auditmanager.',
text_en: 'Pricing is employee-based and market-validated. Companies with over 250 employees pay about EUR 25,000 to 50,000 per year. In return, they save EUR 50,000 or more — on pentests, CE assessments and audit managers.',
pause_after: 2500,
},
{
text_de: 'Die Plattform läuft standardmäßig in der Cloud — BSI-zertifiziert in Deutschland oder OVH in Frankreich. Für Kleinstunternehmen unter 10 Mitarbeitern bieten wir optional einen vorkonfigurierten Mac Mini für absolute Privacy.',
text_en: 'The platform runs by default in the cloud — BSI-certified in Germany or OVH in France. For micro businesses under 10 employees, we optionally offer a pre-configured Mac Mini for absolute privacy.',
text_de: 'Unsere Plattform ist modular aufgebaut — wie ein Baukasten, den jedes Unternehmen nach Bedarf zusammenstellt. Von Code Security über Compliance-Dokumente bis zum Compliance Optimizer — alles aus einer Hand.',
text_en: 'Our platform is modular — like a toolkit that every company assembles as needed. From code security to compliance documents to the Compliance Optimizer — all from a single source.',
pause_after: 2000,
},
{
text_de: 'Standardmässig läuft alles in unserer BSI C5-zertifizierten Cloud in Deutschland. Für Unternehmen, die absolute Datensouveränität wollen, bieten wir optional eine lokale Hardwarelösung an.',
text_en: 'By default everything runs in our BSI C5-certified cloud in Germany. For companies wanting absolute data sovereignty, we optionally offer a local hardware solution.',
pause_after: 1500,
},
],
transition_hint_de: 'Wie funktioniert das konkret?',
transition_hint_en: 'How does this work in practice?',
},
// 5 — how-it-works (50s)
// 7 — how-it-works (50s)
{
slideId: 'how-it-works',
duration: 50,
paragraphs: [
{
text_de: 'Schritt eins: Cloud-Vertrag abschließen. BSI-Cloud in Deutschland oder OVH in Frankreich — fixe oder flexible Kosten, keine US-Anbieter.',
text_en: 'Step one: sign a cloud contract. BSI cloud in Germany or OVH in France — fixed or flexible costs, no US providers.',
text_de: 'In vier einfachen Schritten zur vollständigen Compliance. Schritt eins: Cloud-Vertrag abschliessen. Bundesamt für Sicherheit in der Informationstechnik-Cloud in Deutschland — fixe oder flexible Kosten, volle Transparenz.',
text_en: 'Complete compliance in four simple steps. Step one: sign a cloud contract. BSI cloud in Germany — fixed or flexible costs, full transparency.',
pause_after: 1500,
},
{
text_de: 'Schritt zwei: Code-Repositories verbinden. Die KI scannt sofort auf Schwachstellen und Compliance-Lücken — bei jeder Änderung, nicht einmal im Jahr.',
text_en: 'Step two: connect code repositories. The AI immediately scans for vulnerabilities and compliance gaps on every change, not once a year.',
text_de: 'Schritt zwei: Code-Repositories verbinden. Ab diesem Moment scannt die KI bei jeder Änderung auf Schwachstellen und Compliance-Lücken — nicht einmal im Jahr, sondern bei jedem einzelnen Commit.',
text_en: 'Step two: connect code repositories. From this moment the AI scans for vulnerabilities and compliance gaps on every change not once a year, but on every single commit.',
pause_after: 2000,
},
{
text_de: 'Schritt drei: Compliance und Security laufen automatisch. VVT, TOMs, DSFA, CE-Dokumentation werden kontinuierlich erstellt und aktualisiert.',
text_en: 'Step three: compliance and security run automatically. RoPA, TOMs, DPIA, CE documentation are continuously created and updated.',
text_de: 'Schritt drei: Compliance und Security laufen vollautomatisch. Alle Dokumente werden kontinuierlich erstellt und aktualisiert. Der Audit Manager bereitet alles vor — auf Knopfdruck.',
text_en: 'Step three: compliance and security run fully automatically. All documents are continuously created and updated. The audit manager prepares everything — at the push of a button.',
pause_after: 2000,
},
{
text_de: 'Schritt vier: Audit vorbereiten. Alle Nachweise auf Knopfdruck. Nach dem Audit werden Abweichungen automatisch nachverfolgt — mit Stichtagen, Tickets und Eskalation an die Geschäftsführung.',
text_en: 'Step four: prepare for audit. All evidence at the push of a button. Post-audit, deviations are automatically tracked — with deadlines, tickets and escalation to management.',
text_de: 'Schritt vier: Nach dem Audit werden Abweichungen automatisch nachverfolgt. Mit Stichtagen, Tickets und Eskalation. Das spart nicht nur Geld — es spart Nerven.',
text_en: 'Step four: after the audit, deviations are automatically tracked. With deadlines, tickets and escalation. This saves not just money — it saves nerves.',
pause_after: 1500,
},
],
transition_hint_de: 'Jetzt zur Marktchance.',
transition_hint_en: 'Now to the market opportunity.',
transition_hint_de: 'Jetzt zur Marktchance — und die ist gigantisch.',
transition_hint_en: 'Now to the market opportunity — and it is massive.',
},
// 6 — market (60s)
// 8 — market (60s)
{
slideId: 'market',
duration: 60,
paragraphs: [
{
text_de: 'Der Markt für integrierte Compliance- und Code-Security-Plattformen ist enorm — und weitgehend unbesetzt.',
text_en: 'The market for integrated compliance and code security platforms is enormous — and largely unoccupied.',
text_de: 'Der Markt für integrierte Compliance- und Code-Security-Plattformen ist riesig — und weitgehend unbesetzt. Das ist eine einmalige Chance.',
text_en: 'The market for integrated compliance and code security platforms is enormous — and largely unoccupied. This is a once-in-a-generation opportunity.',
pause_after: 1500,
},
{
text_de: 'Unser Total Addressable Market liegt bei 8,7 Milliarden Euro. Der globale RegTech-Markt wächst mit 23 Prozent pro Jahr, getrieben durch AI Act, CRA und verschärfte DSGVO-Durchsetzung.',
text_en: 'Our Total Addressable Market is EUR 8.7 billion. The global RegTech market grows at 23 percent per year, driven by the AI Act, CRA and stricter GDPR enforcement.',
text_de: 'Der Total Addressable Market liegt bei 8,7 Milliarden Euro. Der globale RegTech-Markt wächst mit 23 Prozent pro Jahr getrieben durch AI Act, Cyber Resilience Act und verschärfte Datenschutz-Grundverordnung-Durchsetzung. Jedes neue Gesetz vergrößert unseren Markt.',
text_en: 'The Total Addressable Market is EUR 8.7 billion. The global RegTech market grows at 23 percent per year driven by the AI Act, CRA and stricter GDPR enforcement. Every new law enlarges our market.',
pause_after: 2000,
},
{
text_de: 'Der Serviceable Addressable Market in DACH: 1,2 Milliarden Euro. Unsere Zielgruppen: Maschinen- und Anlagenbauer, CE-Zertifizierer und alle produzierenden Unternehmen, die Elektronik und Software entwickeln.',
text_en: 'The Serviceable Addressable Market in DACH: EUR 1.2 billion. Our target groups: machine and plant manufacturers, CE certifiers and all manufacturing companies developing electronics and software.',
text_de: 'Unser Serviceable Addressable Market in DACH: 950 Millionen Euro. Maschinen- und Anlagenbauer, C. E. Zertifizierer und alle produzierenden Unternehmen, die Software entwickeln.',
text_en: 'Our Serviceable Addressable Market in DACH: EUR 950 million. Machine and plant manufacturers, CE certifiers and all manufacturing companies developing software.',
pause_after: 2000,
},
{
text_de: 'Unser Serviceable Obtainable Market: 24 Millionen Euro. Über 1.200 Kunden bis 2030 — Maschinen- und Anlagenbauer, Automobilindustrie und Zulieferer, die genau unsere Kombination aus Code-Security und Compliance brauchen.',
text_en: 'Our Serviceable Obtainable Market: EUR 24 million. Over 1,200 customers by 2030 — machine manufacturers, automotive industry and suppliers who need exactly our combination of code security and compliance.',
text_de: 'Und das Schöne daran: Je mehr Regulierung kommt, desto grösser wird unser Markt. Wir profitieren von der steigenden Komplexität, die wir automatisieren.',
text_en: 'And the beautiful thing: the more regulation comes, the larger our market grows. We profit from the increasing complexity that we automate.',
pause_after: 1500,
},
],
@@ -221,24 +241,19 @@ export const PRESENTER_SCRIPT: SlideScript[] = [
transition_hint_en: 'How do we make money?',
},
// 7 — business-model (50s)
// 9 — business-model / Pricing (40s)
{
slideId: 'business-model',
duration: 50,
duration: 40,
paragraphs: [
{
text_de: 'Unser Geschäftsmodell basiert auf einem einfachen Savings-Argument: Kunden zahlen 25.000 bis 50.000 Euro im Jahr — und sparen mehr als sie zahlen.',
text_en: 'Our business model is based on a simple savings argument: customers pay EUR 25,000 to 50,000 per year — and save more than they pay.',
text_de: 'Unser Pricing basiert auf drei Tiers: Starter für kleine Unternehmen ab 3.600 Euro im Jahr. Professional für den Mittelstand ab 15.000 Euro. Und Enterprise für Konzerne ab 50.000 Euro — inklusive dedizierter Instanz und Custom-Integrationen.',
text_en: 'Our pricing is based on three tiers: Starter for small companies from EUR 3,600 per year. Professional for mid-market from EUR 15,000. And Enterprise for large companies from EUR 50,000 — including dedicated instance and custom integrations.',
pause_after: 2000,
},
{
text_de: '30.000 Euro für Pentests gespart, 20.000 Euro für CE-Software-Risikobeurteilungen gespart, einen Auditmanager entlastet oder gar nicht erst eingestellt. Dazu Strafvermeidung und Echtzeit-Reaktion auf Kundenanfragen zu Software-Sicherheit.',
text_en: 'EUR 30,000 saved on pentests, EUR 20,000 saved on CE software risk assessments, an audit manager relieved or never hired. Plus penalty avoidance and real-time response to customer inquiries about software security.',
pause_after: 2500,
},
{
text_de: 'Die Unit Economics: Bruttomarge über 80 Prozent. Cloud-native auf SysEleven, OVH und Hetzner — deutlich günstiger als AWS oder Azure. Mitarbeiterbasiertes Pricing, modular wählbar.',
text_en: 'The unit economics: gross margin above 80 percent. Cloud-native on SysEleven, OVH and Hetzner — significantly cheaper than AWS or Azure. Employee-based pricing, modular choice.',
text_de: 'Dazu kommt Beratung und Service als zusätzliche Einnahmequelle. Das Entscheidende: Unsere Kunden sparen mehr als sie zahlen — durch Einsparungen bei Security Tests, Zertifizierungen, Auditkosten und Entwicklerzeit.',
text_en: 'Additionally, consulting and service as an extra revenue stream. The key point: our customers save more than they pay — through savings on security tests, certifications, audit costs and developer time.',
pause_after: 1500,
},
],
@@ -246,29 +261,24 @@ export const PRESENTER_SCRIPT: SlideScript[] = [
transition_hint_en: 'What have we achieved so far?',
},
// 8 — traction (50s)
// 10 — traction (50s)
{
slideId: 'traction',
duration: 50,
paragraphs: [
{
text_de: 'Unsere bisherige Traction — und das als Bootstrapped-Startup.',
text_en: 'Our traction so far — and all of this as a bootstrapped startup.',
text_de: 'Und jetzt wird es spannend: Was wir bisher geschafft haben — komplett bootstrapped, ohne einen Cent externes Kapital.',
text_en: 'And now it gets exciting: what we have achieved so far — completely bootstrapped, without a single cent of external capital.',
pause_after: 1500,
},
{
text_de: '500.000 Zeilen Code. 45 Container in Produktion. Über 65 Compliance-Module implementiert. 25.000+ Originaldokumente in der RAG-Datenbank. Die komplette Plattform ist funktionsfähig und deployed.',
text_en: '500,000 lines of code. 45 containers in production. Over 65 compliance modules implemented. 25.000+ original documents in the RAG database. The complete platform is functional and deployed.',
text_de: 'Über 500.000 Zeilen Code. 12 Produkt-Module implementiert. Über 380 Gesetze und Regularien in der RAG-Datenbank indexiert mit über 25.000 extrahierten Controls. Die Plattform befindet sich aktuell im Prototyp-Stadium und wird mit Testkunden validiert.',
text_en: 'Over 500,000 lines of code. 12 product modules implemented. Over 380 laws and regulations indexed in the RAG database with over 25,000 extracted controls. The platform is currently in prototype stage and being validated with test customers.',
pause_after: 2000,
},
{
text_de: 'Wir entlasten Kunden konkret in: Audit Management, Evidence Management, automatischem Pentesting, automatischer SBOM-Generierung, vollständigen DSGVO-Prozessen und Schulungsprogrammen.',
text_en: 'We concretely relieve customers in: audit management, evidence management, automated pentesting, automatic SBOM generation, complete GDPR processes and training programs.',
pause_after: 2000,
},
{
text_de: 'Die Plattform ist bereit für die ersten zahlenden Kunden.',
text_en: 'The platform is ready for its first paying customers.',
text_de: 'Der Prototyp läuft, die Testkunden geben uns wertvolles Feedback. Ab August 2026 gehen wir mit der Gründung in den produktiven Betrieb. Was uns jetzt fehlt, ist nicht Technologie — sondern Go-to-Market-Kapital.',
text_en: 'The prototype is running, test customers are giving us valuable feedback. From August 2026 we go live with the company founding. What we lack now is not technology — but go-to-market capital.',
pause_after: 1000,
},
],
@@ -276,251 +286,313 @@ export const PRESENTER_SCRIPT: SlideScript[] = [
transition_hint_en: 'How do we compare to the competition?',
},
// 9 — competition (60s)
// 11 — competition (60s)
{
slideId: 'competition',
duration: 60,
paragraphs: [
{
text_de: 'Kein Wettbewerber kombiniert organisatorische Compliance mit Produkt- und Code-Compliance auf einer Plattform.',
text_en: 'No competitor combines organizational compliance with product and code compliance on a single platform.',
text_de: 'Hier wird es richtig interessant: Kein einziger Wettbewerber kombiniert organisatorische Compliance mit Produkt- und Code-Compliance auf einer Plattform. Wir stehen allein.',
text_en: 'Here it gets really interesting: not a single competitor combines organizational compliance with product and code compliance on a single platform. We stand alone.',
pause_after: 2000,
},
{
text_de: 'Proliance, DataGuard und heyData bieten DSGVO-Compliance — aber keiner scannt Code, keiner macht Penetrationstests, keiner generiert SBOMs, keiner bietet CE-Risikoanalysen.',
text_en: 'Proliance, DataGuard and heyData offer GDPR compliance — but none scan code, none run penetration tests, none generate SBOMs, none offer CE risk assessments.',
text_de: 'Proliance, DataGuard und heyData — alle machen Datenschutz-Grundverordnung. Aber keiner scannt Code, keiner macht Pentests, keiner generiert Software Bill of Materialss, keiner bietet CE-Risikoanalysen. Das ist wie ein Autohaus, das nur die Karosserie verkauft, aber keinen Motor einbaut.',
text_en: 'Proliance, DataGuard and heyData — all do GDPR. But none scan code, none run pentests, none generate SBOMs, none offer CE risk assessments. It is like a car dealership selling only the body but installing no engine.',
pause_after: 2500,
},
{
text_de: 'Vanta und Drata kommen aus den USA mit SOC2-Fokus. Sie nutzen amerikanische Cloud-Provider, verstehen weder CRA noch die spezifischen Anforderungen des europäischen Maschinenbaus — und ihre Daten liegen außerhalb der EU.',
text_en: 'Vanta and Drata come from the US with a SOC2 focus. They use American cloud providers, understand neither CRA nor the specific requirements of European machine manufacturing — and their data resides outside the EU.',
text_de: 'Vanta und Drata? Kommen aus den USA mit SOC2-Fokus. Sie verstehen weder den Cyber Resilience Act noch die Maschinenverordnung — und ihre Daten liegen ausserhalb der EU. Für den europäischen Maschinenbau sind sie schlicht keine Option.',
text_en: 'Vanta and Drata? They come from the US with a SOC2 focus. They understand neither the CRA nor the Machinery Regulation — and their data resides outside the EU. For European machine manufacturing they are simply not an option.',
pause_after: 2500,
},
{
text_de: 'Unser entscheidender Vorteil: Nicht nur organisatorische Compliance, sondern auch Produkt- und Code-Compliance. Reine EU-Hosting-Infrastruktur. Volle Integration von Compliance über Code bis Prozesse.',
text_en: 'Our decisive advantage: Not just organizational compliance, but also product and code compliance. Pure EU hosting infrastructure. Full integration from compliance to code to processes.',
text_de: 'Wir besetzen als Erste einen neuen Markt: KI-gestützte Synchronisation von Unternehmensprozessen, regulatorischen Anforderungen und Source-Code-Implementierung — bei gleichzeitiger Einhaltung kontinuierlicher Security-Anforderungen. Diese Kombination bietet heute kein anderer Anbieter.',
text_en: 'We are the first to occupy a new market: AI-powered synchronization of business processes, regulatory requirements and source code implementation — while maintaining continuous security requirements. No other provider offers this combination today.',
pause_after: 1500,
},
],
transition_hint_de: 'Lernen Sie unser Team kennen.',
transition_hint_en: 'Meet our team.',
transition_hint_de: 'Lernen Sie die Menschen hinter BreakPilot kennen.',
transition_hint_en: 'Meet the people behind BreakPilot.',
},
// 10 — team (30s)
// 12 — team (30s)
{
slideId: 'team',
duration: 30,
paragraphs: [
{
text_de: 'Unser Gründerteam vereint tiefe Domain-Expertise in Compliance, Software-Architektur und KI-Infrastruktur.',
text_en: 'Our founding team combines deep domain expertise in compliance, software architecture and AI infrastructure.',
text_de: 'Unser Gründerteam vereint tiefe Domain-Expertise in Compliance, Software-Architektur und KI-Infrastruktur. Wir haben nicht nur die Vision — wir haben die Plattform gebaut.',
text_en: 'Our founding team combines deep domain expertise in compliance, software architecture and AI infrastructure. We do not just have the vision — we built the platform.',
pause_after: 2000,
},
{
text_de: '500.000 Zeilen Code — von uns geschrieben. Wir kennen jeden Aspekt der Plattform und die Schmerzen der Branche aus erster Hand.',
text_en: '500,000 lines of code — written by us. We know every aspect of the platform and the pain points of the industry firsthand.',
text_de: 'Über 500.000 Zeilen Code. Wir kennen jeden Aspekt der Plattform und die Schmerzen der Branche aus erster Hand. Das ist kein Pitch aus dem Elfenbeinturm — das ist gelebte Erfahrung.',
text_en: 'Over 500,000 lines of code — written by us personally. We know every aspect of the platform and the pain points of the industry firsthand. This is not a pitch from an ivory tower — this is lived experience.',
pause_after: 1500,
},
],
transition_hint_de: 'Schauen wir uns die Finanzprognosen an.',
transition_hint_en: 'Let us look at the financial projections.',
},
// 11 — financials (45s)
{
slideId: 'financials',
duration: 45,
paragraphs: [
{
text_de: 'Unsere Finanzprognose basiert auf einer AI-First Kostenstruktur. 10x Kunden bedeutet nicht 10x Personal — die KI-Agenten skalieren mit.',
text_en: 'Our financial projection is based on an AI-first cost structure. 10x customers does not mean 10x headcount — the AI agents scale with us.',
pause_after: 2000,
},
{
text_de: 'Von 36.000 Euro Umsatz in 2026 auf 8,4 Millionen Euro in 2030. Das Team wächst dabei nur von 2 auf 18 Personen. 380 Kunden bei 5,5 Millionen Euro ARR.',
text_en: 'From EUR 36,000 revenue in 2026 to EUR 8.4 million in 2030. The team grows from just 2 to 18 people. 380 customers at EUR 5.5 million ARR.',
pause_after: 2000,
},
{
text_de: 'Infrastrukturkosten bleiben niedrig dank europäischer Provider. SysEleven, OVH und Hetzner kosten einen Bruchteil von AWS. Break-Even erreichen wir voraussichtlich Ende 2028.',
text_en: 'Infrastructure costs remain low thanks to European providers. SysEleven, OVH and Hetzner cost a fraction of AWS. We expect to reach break-even by end of 2028.',
pause_after: 2000,
},
],
transition_hint_de: 'Und damit kommen wir zum Ask.',
transition_hint_en: 'And that brings us to the ask.',
},
// 12 — the-ask (45s)
// — the-ask (45s)
{
slideId: 'the-ask',
duration: 45,
paragraphs: [
{
text_de: 'Wir suchen eine Pre-Seed Finanzierung für den Go-to-Market.',
text_en: 'We are seeking pre-seed funding for go-to-market.',
text_de: 'Wir suchen eine Pre-Seed-Finanzierung über ein Wandeldarlehen — und das aus gutem Grund: Dieses Instrument gibt beiden Seiten maximale Flexibilität.',
text_en: 'We are seeking pre-seed funding via a convertible loan — and for good reason: this instrument gives both sides maximum flexibility.',
pause_after: 1500,
},
{
text_de: 'Das Investment fließt in vier Bereiche: Engineering für die Enterprise-Features und Skalierung der Cloud-Infrastruktur. Vertrieb für die ersten Pilotkunden im Maschinen- und Anlagenbau. Hardware-Bestand für das Kleinunternehmen-Segment. Und eine Reserve für regulatorische Anforderungen.',
text_en: 'The investment flows into four areas: Engineering for enterprise features and cloud infrastructure scaling. Sales for the first pilot customers in machine and plant manufacturing. Hardware inventory for the small business segment. And a reserve for regulatory requirements.',
text_de: 'Das Wandeldarlehen über 200.000 Euro — bestehend aus 40.000 Euro Investor-Anteil und 160.000 Euro L-Bank Co-Finanzierung — fließt primär in Personal, Cloud-Infrastruktur und die ersten Vertriebsaktivitäten. Bei Bedarf kann die Höhe flexibel auf bis zu 400.000 Euro verdoppelt werden.',
text_en: 'The convertible loan of EUR 200,000 — consisting of EUR 40,000 investor share and EUR 160,000 L-Bank co-financing — flows primarily into personnel, cloud infrastructure and initial sales activities. If needed, the amount can be flexibly doubled to up to EUR 400,000.',
pause_after: 2500,
},
{
text_de: 'Mit diesem Kapital erreichen wir die ersten 20 zahlenden Kunden und beweisen Product-Market-Fit — bei Maschinen- und Anlagenbauern, CE-Zertifizierern und produzierenden Unternehmen.',
text_en: 'With this capital we reach our first 20 paying customers and prove product-market fit — with machine and plant manufacturers, CE certifiers and manufacturing companies.',
text_de: 'Besonders attraktiv für Sie als Investor: Über das BAFA INVEST-Programm erhalten Sie bis zu 15 Prozent Ihres Investments als steuerfreien Zuschuss zurück — plus 25 Prozent Exit-Zuschuss auf Ihre Gewinne. Und die staatliche Co-Finanzierung durch die L-Bank Pre-Seed reduziert Ihr Risiko erheblich.',
text_en: 'Particularly attractive for you as an investor: Through the BAFA INVEST program you receive up to 15 percent of your investment back as a tax-free grant — plus 25 percent exit grant on your gains. And government co-financing through L-Bank Pre-Seed significantly reduces your risk.',
pause_after: 1500,
},
],
transition_hint_de: 'Haben Sie Fragen? Unser KI-Agent steht bereit.',
transition_hint_en: 'Have questions? Our AI agent is ready.',
transition_hint_de: 'Schauen wir uns die Kundenersparnis im Detail an.',
transition_hint_en: 'Let us look at customer savings in detail.',
},
// 13 — ai-qa (30s)
// — customer-savings (45s)
{
slideId: 'customer-savings',
duration: 45,
paragraphs: [
{
text_de: 'Diese Folie ist entscheidend: Hier sehen Sie Zeile für Zeile, was Kunden heute ohne uns bezahlen — und was sie mit BreakPilot sparen. Jede Anwendung braucht einen eigenen Pentest. Jedes Produkt eine eigene CE-Beurteilung. Diese Kosten skalieren linear — unsere Plattform nicht.',
text_en: 'This slide is crucial: Here you see line by line what customers pay today without us — and what they save with BreakPilot. Each application needs its own pentest. Each product its own CE assessment. These costs scale linearly — our platform does not.',
pause_after: 2500,
},
{
text_de: 'Die konkreten Einsparpotentiale sind erheblich — bei Rechtsberatung, Security Tests, Auditkosten und Entwicklerzeit. Je größer das Unternehmen und je mehr Anwendungen und Produkte, desto höher die Ersparnis. Das sind keine Schätzungen — das sind nachrechenbare Positionen.',
text_en: 'The concrete savings potential is substantial — in legal consulting, security tests, audit costs and developer time. The larger the company and the more applications and products, the higher the savings. These are not estimates — these are verifiable line items.',
pause_after: 2000,
},
],
transition_hint_de: 'Jetzt können Sie uns Ihre Fragen stellen.',
transition_hint_en: 'Now you can ask us your questions.',
},
// — ai-qa (30s)
{
slideId: 'ai-qa',
duration: 30,
paragraphs: [
{
text_de: 'Auf dieser Slide können Sie direkt mit unserem KI-Agent interagieren. Stellen Sie Ihre Investorenfragen — der Agent antwortet mit Echtdaten aus unserer Plattform.',
text_en: 'On this slide you can interact directly with our AI agent. Ask your investor questions — the agent responds with real data from our platform.',
text_de: 'Auf dieser Folie interagieren Sie direkt mit unserem KI-Agenten. Fragen Sie alles, was Sie als Investor wissen müssen — der Agent antwortet mit Echtdaten aus unserer Plattform.',
text_en: 'On this slide you interact directly with our AI agent. Ask everything you need to know as an investor — the agent responds with real data from our platform.',
pause_after: 2000,
},
{
text_de: 'Nutzen Sie den Chat rechts unten oder die vorgeschlagenen Fragen.',
text_en: 'Use the chat in the bottom right or the suggested questions.',
text_de: 'Nutzen Sie den Chat rechts unten oder klicken Sie auf eine der vorgeschlagenen Fragen. Der Agent kennt unsere Zahlen, unsere Strategie und unsere Technologie im Detail.',
text_en: 'Use the chat in the bottom right or click on one of the suggested questions. The agent knows our numbers, our strategy and our technology in detail.',
pause_after: 1500,
},
],
transition_hint_de: 'Im Anhang finden Sie weitere Details.',
transition_hint_en: 'You will find further details in the appendix.',
transition_hint_de: 'Im Anhang finden Sie weitere Details — Strategie, Finanzplan, Annahmen und technische Architektur.',
transition_hint_en: 'You will find further details in the appendix — strategy, financial plan, assumptions and technical architecture.',
},
// 14 — annex-assumptions (35s)
// — annex-strategy (35s)
{
slideId: 'annex-strategy',
duration: 35,
paragraphs: [
{
text_de: 'Unsere Wachstumsstrategie basiert auf zwei Channel-Partnern: CANCOM Cloud Marketplace für schnellen Einstieg und Bechtle für nationale Reichweite. Channel-Vertrieb skaliert exponentiell — weil die Partner ihre eigenen Teams auf unser Produkt schulen.',
text_en: 'Our growth strategy is based on two channel partners: CANCOM Cloud Marketplace for fast entry and Bechtle for national reach. Channel sales scales exponentially — because partners train their own teams on our product.',
pause_after: 2000,
},
{
text_de: 'Deshalb stellen wir den Channel Manager vor dem ersten Direktvertriebler ein. Ein einziger Bechtle-Deal bringt zehn bis fünfzig Endkunden.',
text_en: 'That is why we hire the channel manager before the first direct salesperson. A single Bechtle deal brings ten to fifty end customers.',
pause_after: 1500,
},
],
},
// — annex-finanzplan (30s)
{
slideId: 'annex-finanzplan',
duration: 30,
paragraphs: [
{
text_de: 'Der detaillierte Finanzplan — hier können Sie in die Rohdaten eintauchen. Personalkosten, betriebliche Aufwendungen, Umsatzerlöse, Liquidität und GuV — alles monatlich aufgeschlüsselt über fünf Jahre. Alle Werte in Euro.',
text_en: 'The detailed financial plan — here you can dive into the raw data. Personnel costs, operating expenses, revenue, liquidity and P and L — all broken down monthly over five years. All values in EUR.',
pause_after: 2000,
},
{
text_de: 'Alle Kennzahlen und Grafiken in der Präsentation werden aus diesen Daten berechnet. Nichts ist erfunden — alles ist nachvollziehbar.',
text_en: 'All KPIs and charts in the presentation are calculated from this data. Nothing is invented — everything is traceable.',
pause_after: 1500,
},
],
},
// — annex-assumptions (35s)
{
slideId: 'annex-assumptions',
duration: 35,
paragraphs: [
{
text_de: 'Im Anhang: Unsere Annahmen und Sensitivitätsanalyse. Drei Szenarien — konservativ, base case und optimistisch — für robuste Planung.',
text_en: 'In the appendix: Our assumptions and sensitivity analysis. Three scenarios — conservative, base case and optimistic — for robust planning.',
text_de: 'Unsere Annahmen und Sensitivitätsanalyse. Drei Szenarien — konservativ, Basisplan und optimistisch — für robuste Planung. Alle Werte werden aus dem Finanzplan berechnet.',
text_en: 'Our assumptions and sensitivity analysis. Three scenarios — conservative, base plan and optimistic — for robust planning. All values are computed from the financial plan.',
pause_after: 2000,
},
{
text_de: 'Alle Finanzprognosen basieren auf validierten Marktdaten und berücksichtigen die Kostenvorteile europäischer Cloud-Infrastruktur gegenüber US-Anbietern.',
text_en: 'All financial projections are based on validated market data and account for the cost advantages of European cloud infrastructure over US providers.',
text_de: 'Alle Prognosen basieren auf validierten Marktdaten. Die Kostenvorteile europäischer Cloud-Infrastruktur gegenüber US-Anbietern sind dabei bereits eingepreist.',
text_en: 'All projections are based on validated market data. The cost advantages of European cloud infrastructure over US providers are already factored in.',
pause_after: 1500,
},
],
},
// 15 — annex-architecture (40s)
{
slideId: 'annex-architecture',
duration: 40,
paragraphs: [
{
text_de: 'Die technische Architektur: Cloud-SDK-Plattform mit BSI-zertifizierter Infrastruktur in Deutschland und Frankreich. 1000-Milliarden-Parameter LLM, isolierte Kunden-Namespaces, kein Datentransfer außerhalb der EU.',
text_en: 'The technical architecture: Cloud SDK platform with BSI-certified infrastructure in Germany and France. 1000-billion-parameter LLM, isolated customer namespaces, no data transfer outside the EU.',
pause_after: 2000,
},
{
text_de: 'Für Kleinunternehmen ergänzend: lokale Hardware mit kleineren LLMs für Dokumentenverarbeitung und Prozessautomatisierung. BreakPilot hat nur Wartungszugriff — keine Einsicht in Kundendaten.',
text_en: 'For small companies additionally: local hardware with smaller LLMs for document processing and process automation. BreakPilot only has maintenance access — no visibility into customer data.',
pause_after: 1500,
},
],
},
// 16 — annex-gtm (40s)
{
slideId: 'annex-gtm',
duration: 40,
paragraphs: [
{
text_de: 'Unsere Go-to-Market Strategie: Drei Zielgruppen — Maschinen- und Anlagenbauer, CE-Zertifizierer und alle produzierenden Unternehmen, die Elektronik und Software entwickeln.',
text_en: 'Our go-to-market strategy: Three target groups — machine and plant manufacturers, CE certifiers and all manufacturing companies developing electronics and software.',
pause_after: 2000,
},
{
text_de: 'Wir starten mit VDMA-Mitgliedern, skalieren über Partnerschaften mit Systemhäusern und erschließen dann den breiteren produzierenden Mittelstand.',
text_en: 'We start with VDMA members, scale through partnerships with system integrators and then tap into the broader manufacturing mid-market.',
pause_after: 1500,
},
],
},
// 17 — annex-regulatory (40s)
// — annex-regulatory (40s)
{
slideId: 'annex-regulatory',
duration: 40,
paragraphs: [
{
text_de: 'Die EU-Regulierungslandschaft für produzierende Unternehmen: DSGVO, AI Act, Cyber Resilience Act, NIS2 und CE-Kennzeichnung. Alle Regularien zusammen erzeugen massiven Compliance-Druck.',
text_en: 'The EU regulatory landscape for manufacturing companies: GDPR, AI Act, Cyber Resilience Act, NIS2 and CE certification. All regulations together create massive compliance pressure.',
text_de: 'Die EU-Regulierungslandschaft für produzierende Unternehmen wird immer komplexer: Datenschutz-Grundverordnung, AI Act, Cyber Resilience Act, NIS 2 Richtlinie und C. E. Kennzeichnung. Jedes einzelne Gesetz erzeugt enormen Compliance-Druck — zusammen sind sie für kleine und mittlere Unternehmen kaum noch beherrschbar.',
text_en: 'The EU regulatory landscape for manufacturing companies is becoming increasingly complex: GDPR, AI Act, CRA, NIS2 and CE certification. Each law creates enormous compliance pressure — together they are almost unmanageable for SMEs.',
pause_after: 2000,
},
{
text_de: 'Unsere RAG-Datenbank enthält 25.000+ Originaldokumente und über 25.000 extrahierte Controls. Die KI kennt jede Verordnung, jede Richtlinie, jedes Gesetz — und kann auf jede Compliance-Frage sofort antworten.',
text_en: 'Our RAG database contains 25.000+ original documents and over 25,000 extracted controls. The AI knows every regulation, every directive, every law — and can answer every compliance question immediately.',
text_de: 'Unsere RAG-Datenbank enthält über 380 vollständig indexierte Gesetze, Regularien und rechtliche Dokumente mit über 25.000 extrahierten Controls. Die KI kann auf jede Compliance-Frage sofort antworten — präzise und mit Quellenangabe.',
text_en: 'Our RAG database contains over 380 fully indexed laws, regulations and legal documents with over 25,000 extracted controls. The AI can answer every compliance question immediately — precisely and with source reference.',
pause_after: 1500,
},
],
},
// 18 — annex-engineering (40s)
// — annex-architecture (40s)
{
slideId: 'annex-architecture',
duration: 40,
paragraphs: [
{
text_de: 'Die technische Architektur: Cloud-SDK-Plattform mit BSI C5-zertifizierter Infrastruktur in Deutschland. Isolierte Kunden-Namespaces, kein Datentransfer ausserhalb der EU. Die Architektur ist Enterprise-ready und horizontal skalierbar.',
text_en: 'The technical architecture: Cloud SDK platform with BSI C5-certified infrastructure in Germany. Isolated customer namespaces, no data transfer outside the EU. The architecture is enterprise-ready and horizontally scalable.',
pause_after: 2000,
},
{
text_de: 'Für Kleinunternehmen ergänzend: lokale Hardware mit optimierten LLMs für Dokumentenverarbeitung. BreakPilot hat nur Wartungszugriff — keine Einsicht in Kundendaten. Absolute Datensouveränität.',
text_en: 'For small companies additionally: local hardware with optimized LLMs for document processing. BreakPilot only has maintenance access — no visibility into customer data. Absolute data sovereignty.',
pause_after: 1500,
},
],
},
// — annex-engineering (40s)
{
slideId: 'annex-engineering',
duration: 40,
paragraphs: [
{
text_de: 'Engineering Deep Dive: 481.000 Zeilen Code, 10 Container, 48 Compliance-Module. Tech-Stack: Go, Python, TypeScript mit Next.js. CI/CD über Gitea Actions, automatisches Deploy via Coolify auf Hetzner.',
text_en: 'Engineering deep dive: 481,000 lines of code, 10 containers, 48 compliance modules. Tech stack: Go, Python, TypeScript with Next.js. CI/CD via Gitea Actions, automatic deploy via Coolify on Hetzner.',
text_de: 'Engineering Deep Dive: Über 500.000 Zeilen Code, 12 Produkt-Module. Tech-Stack: Go, Python, TypeScript mit Next.js. CI/CD über Gitea Actions mit automatischem Deploy via Orca.',
text_en: 'Engineering deep dive: Over 500,000 lines of code, 12 product modules. Tech stack: Go, Python, TypeScript with Next.js. CI/CD via Gitea Actions with automatic deploy via Orca.',
pause_after: 2000,
},
{
text_de: 'Infrastruktur: 100 Prozent EU-Cloud. PostgreSQL und Qdrant auf Hetzner, 120-Milliarden-Parameter-LLM auf OVH, 1000-Milliarden-Parameter-LLM auf SysEleven — BSI-zertifiziert. Keine US-Anbieter.',
text_en: 'Infrastructure: 100 percent EU cloud. PostgreSQL and Qdrant on Hetzner, 120 billion parameter LLM on OVH, 1 trillion parameter LLM on SysEleven — BSI certified. No US providers.',
text_de: '100 Prozent EU-Cloud. PostgreSQL und Qdrant auf Hetzner, LLMs auf SysEleven — BSI C5-zertifiziert. Kein einziger US-Anbieter im gesamten Stack. Das ist nicht nur ein Feature — das ist unser Versprechen.',
text_en: '100 percent EU cloud. PostgreSQL and Qdrant on Hetzner, LLMs on SysEleven — BSI C5 certified. Not a single US provider in the entire stack. That is not just a feature — that is our promise.',
pause_after: 1500,
},
],
},
// 19 — annex-aipipeline (40s)
// — annex-aipipeline (40s)
{
slideId: 'annex-aipipeline',
duration: 40,
paragraphs: [
{
text_de: 'Die KI-Pipeline: 38 Verordnungen indexiert, 6.259 Controls extrahiert, 325 Pflichten aus 9 Regulierungen abgeleitet. RAG mit 6 Qdrant-Collections, BGE-M3 Embeddings, Hybrid Search mit Cross-Encoder Re-Ranking.',
text_en: 'The AI pipeline: 38 regulations indexed, 6,259 controls extracted, 325 obligations derived from 9 regulations. RAG with 6 Qdrant collections, BGE-M3 embeddings, hybrid search with cross-encoder re-ranking.',
text_de: 'Die KI-Pipeline im Detail: Über 380 Regularien indexiert, über 25.000 Controls extrahiert. RAG mit sechs Qdrant-Collections, BGE-M3 Embeddings und Hybrid Search mit Cross-Encoder Re-Ranking.',
text_en: 'The AI pipeline in detail: Over 380 regulations indexed, over 25,000 controls extracted. RAG with six Qdrant collections, BGE-M3 embeddings and hybrid search with cross-encoder re-ranking.',
pause_after: 2500,
},
{
text_de: 'Kernprinzip: Das LLM ist nicht die Wahrheitsquelle. Wahrheit gleich Regeln plus Evidenz. Das LLM ist Übersetzer und Subsumtions-Helfer. Deterministische Policy Engine mit 45 Regeln und Eskalationsstufen E0 bis E3.',
text_en: 'Core principle: The LLM is not the source of truth. Truth equals rules plus evidence. The LLM is translator and subsumption helper. Deterministic policy engine with 45 rules and escalation levels E0 to E3.',
text_de: 'Unser Kernprinzip: Das LLM ist nicht die Wahrheitsquelle. Wahrheit entsteht aus Regeln plus Evidenz. Das LLM ist Übersetzer und Subsumtionshelfer. Eine deterministische Policy Engine sorgt dafür, dass kein Compliance-Fehler durchrutscht.',
text_en: 'Our core principle: The LLM is not the source of truth. Truth comes from rules plus evidence. The LLM is translator and subsumption helper. A deterministic policy engine ensures no compliance error slips through.',
pause_after: 2000,
},
],
},
// 20 — annex-sdk-demo (45s)
// — annex-sdk-demo (45s)
{
slideId: 'annex-sdk-demo',
duration: 45,
paragraphs: [
{
text_de: 'Zum Abschluss: Echte Screenshots aus unserem Compliance SDK. Sie sehen hier das Projekt der Müller Maschinenbau GmbH — ein typischer mittelständischer Maschinenbauer mit KI-Projekten.',
text_en: 'To conclude: Real screenshots from our Compliance SDK. You can see the project of Müller Maschinenbau GmbH — a typical mid-sized machine builder with AI projects.',
text_de: 'Echte Screenshots aus unserem Compliance SDK im Einsatz. Sie sehen hier das Projekt der Müller Maschinenbau GmbH — ein typischer mittelständischer Maschinenbauer mit KI-Projekten.',
text_en: 'Real screenshots from our Compliance SDK in action. You can see the project of Müller Maschinenbau GmbH — a typical mid-sized machine builder with AI projects.',
pause_after: 2500,
},
{
text_de: 'Von der Risikomatrix über das Verarbeitungsverzeichnis bis zum Dokumenten-Generator — alle Module arbeiten zusammen. Das ist keine Mockup-Präsentation, sondern eine produktive Plattform.',
text_en: 'From the risk matrix to the processing register to the document generator — all modules work together. This is not a mockup presentation, but a productive platform.',
text_de: 'Von der Risikomatrix über das Verarbeitungsverzeichnis bis zum Dokumenten-Generator — alle Module arbeiten nahtlos zusammen. Das ist ein funktionierender Prototyp, der aktuell mit Testkunden validiert wird.',
text_en: 'From the risk matrix to the processing register to the document generator — all modules work seamlessly together. This is a working prototype currently being validated with test customers.',
pause_after: 2000,
},
],
},
// — risks (45s)
{
slideId: 'risks',
duration: 45,
paragraphs: [
{
text_de: 'Jetzt zu den Risiken — denn Transparenz ist uns wichtig. Wir haben sechs wesentliche Risiken identifiziert und für jedes eine konkrete Mitigation erarbeitet.',
text_en: 'Now to the risks — because transparency is important to us. We have identified six key risks and developed a concrete mitigation for each.',
pause_after: 2000,
},
{
text_de: 'Das größte Risiko ist die Commoditisierung durch KI. Unsere Antwort: Wir konkurrieren nicht auf der Ebene des KI-Wissens, sondern auf der Ebene der vertrauenswürdigen Infrastruktur. KI ist unser Multiplikator, nicht unser Produkt.',
text_en: 'The biggest risk is commoditization through AI. Our answer: we don\'t compete at the AI knowledge level, but at the trustworthy infrastructure level. AI is our multiplier, not our product.',
pause_after: 2500,
},
{
text_de: 'Der entscheidende Satz für Investoren: Wir konkurrieren nicht mit KI. Wir konkurrieren mit Teams, die KI besser einsetzen als wir. Deshalb bauen wir nicht das beste Sprachmodell — sondern die vertrauenswürdigste Compliance-Infrastruktur Europas.',
text_en: 'The key statement for investors: We don\'t compete with AI. We compete with teams that use AI better than we do. That\'s why we don\'t build the best language model — but the most trustworthy compliance infrastructure in Europe.',
pause_after: 2000,
},
],
},
// — annex-glossary (20s)
{
slideId: 'annex-glossary',
duration: 20,
paragraphs: [
{
text_de: 'Im Glossar finden Sie alle Fachbegriffe und Abkürzungen erklärt. Falls Ihnen ein Begriff unklar ist, fragen Sie gerne unseren KI-Agenten.',
text_en: 'The glossary explains all technical terms and abbreviations. If any term is unclear, feel free to ask our AI agent.',
pause_after: 1500,
},
],
},
// — legal-disclaimer (20s)
{
slideId: 'legal-disclaimer',
duration: 20,
paragraphs: [
{
text_de: 'Zum Abschluss der wichtige rechtliche Hinweis: Dieses Pitch Deck ist vertraulich und ausschliesslich für den eingeladenen Empfänger bestimmt. Alle Finanzangaben sind Planzahlen. Vielen Dank für Ihre Zeit und Ihr Interesse an BreakPilot COMPLAI.',
text_en: 'To conclude, the important legal notice: This pitch deck is confidential and intended exclusively for the invited recipient. All financial figures are projections. Thank you for your time and interest in BreakPilot COMPLAI.',
pause_after: 1500,
},
],
},
]
export function getScriptForSlide(slideId: string): SlideScript | undefined {
+6 -6
View File
@@ -6,6 +6,7 @@ export const SLIDE_ORDER: SlideId[] = [
'cover',
'problem',
'solution',
'usp',
'regulatory-landscape',
'product',
'how-it-works',
@@ -14,21 +15,20 @@ export const SLIDE_ORDER: SlideId[] = [
'traction',
'competition',
'team',
'financials',
'the-ask',
'cap-table',
'customer-savings',
'ai-qa',
'annex-strategy',
'annex-finanzplan',
'annex-assumptions',
'annex-architecture',
'annex-gtm',
'annex-regulatory',
'annex-architecture',
'annex-engineering',
'annex-aipipeline',
'annex-sdk-demo',
'annex-strategy',
'annex-finanzplan',
'risks',
'annex-glossary',
'legal-disclaimer',
]
export const TOTAL_SLIDES = SLIDE_ORDER.length
+3
View File
@@ -227,6 +227,7 @@ export type SlideId =
| 'cover'
| 'problem'
| 'solution'
| 'usp'
| 'regulatory-landscape'
| 'product'
| 'how-it-works'
@@ -250,3 +251,5 @@ export type SlideId =
| 'annex-strategy'
| 'annex-finanzplan'
| 'annex-glossary'
| 'risks'
| 'legal-disclaimer'
+123
View File
@@ -0,0 +1,123 @@
# BreakPilot Pitch MCP Server
MCP server that lets Claude Code directly manage pitch versions, invite investors, and assign versions — without touching the browser admin UI.
## What it does
11 tools exposed to Claude Code:
| Tool | Description |
|------|-------------|
| `list_versions` | List all pitch versions with status + investor counts |
| `create_version` | Create a draft (snapshot base tables or fork from parent) |
| `get_version` | Get full version detail with all 12 data table snapshots |
| `get_table_data` | Get one table's data (company, team, financials, market, etc.) |
| `update_table_data` | Replace a table's data in a draft version |
| `commit_version` | Lock a draft as immutable |
| `fork_version` | Create new draft by copying an existing version |
| `diff_versions` | Per-table diff between any two versions |
| `list_investors` | List all investors with stats + version assignments |
| `assign_version` | Assign a committed version to an investor |
| `invite_investor` | Send magic-link email to a new investor |
All actions go through the existing admin API at `pitch.breakpilot.com`, so they show up in the audit log.
## Setup
### 1. Build
```bash
cd pitch-deck/mcp-server
npm install
npm run build
```
### 2. Get the admin secret
The `PITCH_ADMIN_SECRET` is stored in orca secrets on the server. SSH in and retrieve it:
```bash
ssh breakpilot-infra-vm1
cat ~/orca/services/breakpilot-dsms/secrets.json | grep PITCH_ADMIN_SECRET
```
### 3. Configure
Edit `.mcp.json` in the **breakpilot-core** repo root (already created):
```json
{
"mcpServers": {
"pitch-versions": {
"command": "node",
"args": ["pitch-deck/mcp-server/dist/index.js"],
"env": {
"PITCH_API_URL": "https://pitch.breakpilot.com",
"PITCH_ADMIN_SECRET": "paste-your-secret-here"
}
}
}
}
```
> **Important:** `.mcp.json` contains a secret. It's already in `.gitignore` — never commit it.
### 4. Restart Claude Code
Exit and reopen Claude Code, or run `/mcp` to check it loaded:
```
/mcp
```
You should see `pitch-versions` listed with 11 tools.
## Usage examples
Just talk to Claude naturally:
- **"List all pitch versions"** → calls `list_versions`
- **"Create a new version called 'Series A Aggressive'"** → calls `create_version`
- **"Show me the company data from version X"** → calls `get_table_data`
- **"Update the company tagline to 'AI-Powered Compliance' in version X"** → calls `update_table_data`
- **"Commit version X"** → calls `commit_version`
- **"Fork version X into a new draft called 'Conservative'"** → calls `fork_version`
- **"Compare version X with version Y"** → calls `diff_versions`
- **"Assign version X to investor jane@vc.com"** → calls `assign_version`
- **"Invite john@fund.com from Big Fund"** → calls `invite_investor`
## Data tables
Each version stores 12 data tables as JSONB snapshots:
| Table | Content |
|-------|---------|
| `company` | Name, tagline (DE/EN), mission (DE/EN), website, HQ city |
| `team` | Members with roles, bios, equity, expertise (all bilingual) |
| `financials` | Year-by-year revenue, costs, MRR, ARR, customers, employees |
| `market` | TAM/SAM/SOM with values, growth rates, sources |
| `competitors` | Names, customer counts, pricing, strengths, weaknesses |
| `features` | Feature comparison matrix (BreakPilot vs competitors) |
| `milestones` | Timeline with dates, titles, descriptions, status (bilingual) |
| `metrics` | Key metrics with labels (bilingual) and values |
| `funding` | Round details, amount, instrument, use of funds breakdown |
| `products` | Product tiers with pricing, LLM specs, features (bilingual) |
| `fm_scenarios` | Financial model scenario names, colors, default flag |
| `fm_assumptions` | Per-scenario assumptions (growth rate, ARPU, churn, etc.) |
## Architecture
```
Claude Code ←stdio→ MCP Server ←HTTP→ pitch.breakpilot.com/api/admin/*
(local) (deployed on orca)
```
The MCP server is a thin HTTP client. All auth, validation, and audit logging happens on the server side. The bearer token authenticates as a CLI admin actor.
## Troubleshooting
**"PITCH_ADMIN_SECRET is required"** → The env var is missing in `.mcp.json`
**401 errors** → The secret is wrong or the pitch-deck container isn't running. Check: `curl -s -H "Authorization: Bearer YOUR_SECRET" https://pitch.breakpilot.com/api/admin/investors`
**MCP server not showing in `/mcp`** → Make sure you're in the `breakpilot-core` directory when you launch Claude Code (`.mcp.json` is project-scoped)
File diff suppressed because it is too large Load Diff
+19
View File
@@ -0,0 +1,19 @@
{
"name": "breakpilot-pitch-mcp",
"version": "1.0.0",
"description": "MCP server for managing BreakPilot pitch versions via Claude Code",
"private": true,
"type": "module",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.12.1"
},
"devDependencies": {
"typescript": "^5.7.2",
"@types/node": "^22.10.2"
}
}
+285
View File
@@ -0,0 +1,285 @@
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const API_URL = process.env.PITCH_API_URL || "https://pitch.breakpilot.com";
const API_SECRET = process.env.PITCH_ADMIN_SECRET || "";
if (!API_SECRET) {
console.error("PITCH_ADMIN_SECRET is required");
process.exit(1);
}
// --- HTTP client ---
async function api(
method: string,
path: string,
body?: unknown
): Promise<unknown> {
const url = `${API_URL}${path}`;
const res = await fetch(url, {
method,
headers: {
Authorization: `Bearer ${API_SECRET}`,
"Content-Type": "application/json",
},
body: body ? JSON.stringify(body) : undefined,
});
const text = await res.text();
let data: unknown;
try {
data = JSON.parse(text);
} catch {
data = text;
}
if (!res.ok) {
const msg =
typeof data === "object" && data && "error" in data
? (data as { error: string }).error
: `HTTP ${res.status}`;
throw new Error(msg);
}
return data;
}
const TABLE_NAMES = [
"company",
"team",
"financials",
"market",
"competitors",
"features",
"milestones",
"metrics",
"funding",
"products",
"fm_scenarios",
"fm_assumptions",
] as const;
// --- MCP Server ---
const server = new McpServer({
name: "breakpilot-pitch",
version: "1.0.0",
});
// 1. list_versions
server.tool(
"list_versions",
"List all pitch versions with status, parent chain, and investor assignment counts",
{},
async () => {
const data = await api("GET", "/api/admin/versions");
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
}
);
// 2. create_version
server.tool(
"create_version",
"Create a new draft version. Optionally fork from a parent version ID, otherwise snapshots current base tables.",
{
name: z.string().describe("Version name, e.g. 'Conservative Q4'"),
description: z
.string()
.optional()
.describe("Optional description"),
parent_id: z
.string()
.uuid()
.optional()
.describe("UUID of parent version to fork from. Omit to snapshot base tables."),
},
async ({ name, description, parent_id }) => {
const data = await api("POST", "/api/admin/versions", {
name,
description,
parent_id,
});
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
}
);
// 3. get_version
server.tool(
"get_version",
"Get full version detail including all 12 data table snapshots",
{
version_id: z.string().uuid().describe("Version UUID"),
},
async ({ version_id }) => {
const data = await api("GET", `/api/admin/versions/${version_id}`);
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
}
);
// 4. get_table_data
server.tool(
"get_table_data",
"Get a specific table's data for a version. Tables: company, team, financials, market, competitors, features, milestones, metrics, funding, products, fm_scenarios, fm_assumptions",
{
version_id: z.string().uuid().describe("Version UUID"),
table_name: z
.enum(TABLE_NAMES)
.describe("Which data table to retrieve"),
},
async ({ version_id, table_name }) => {
const data = await api(
"GET",
`/api/admin/versions/${version_id}/data/${table_name}`
);
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
}
);
// 5. update_table_data
server.tool(
"update_table_data",
"Replace a table's data in a DRAFT version. Pass the full array of row objects. Single-record tables (company, funding) should still be wrapped in an array.",
{
version_id: z.string().uuid().describe("Version UUID (must be a draft)"),
table_name: z.enum(TABLE_NAMES).describe("Which data table to update"),
data: z
.string()
.describe(
"JSON string of the new data — an array of row objects. Example for company: [{\"name\":\"BreakPilot\",\"tagline_en\":\"...\"}]"
),
},
async ({ version_id, table_name, data: dataStr }) => {
let parsed: unknown;
try {
parsed = JSON.parse(dataStr);
} catch {
return {
content: [{ type: "text", text: "Error: invalid JSON in data parameter" }],
isError: true,
};
}
const result = await api(
"PUT",
`/api/admin/versions/${version_id}/data/${table_name}`,
{ data: parsed }
);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
);
// 6. commit_version
server.tool(
"commit_version",
"Commit a draft version, making it immutable and available for investor assignment",
{
version_id: z.string().uuid().describe("Draft version UUID to commit"),
},
async ({ version_id }) => {
const data = await api(
"POST",
`/api/admin/versions/${version_id}/commit`
);
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
}
);
// 7. fork_version
server.tool(
"fork_version",
"Create a new draft by forking an existing version (copies all data)",
{
version_id: z.string().uuid().describe("Version UUID to fork from"),
name: z.string().describe("Name for the new forked draft"),
},
async ({ version_id, name }) => {
const data = await api(
"POST",
`/api/admin/versions/${version_id}/fork`,
{ name }
);
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
}
);
// 8. diff_versions
server.tool(
"diff_versions",
"Compare two versions and see per-table diffs (added/removed/changed rows and fields)",
{
version_a: z.string().uuid().describe("First version UUID"),
version_b: z.string().uuid().describe("Second version UUID"),
},
async ({ version_a, version_b }) => {
const data = await api(
"GET",
`/api/admin/versions/${version_a}/diff/${version_b}`
);
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
}
);
// 9. list_investors
server.tool(
"list_investors",
"List all investors with their login stats, assigned version, and activity",
{},
async () => {
const data = await api("GET", "/api/admin/investors");
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
}
);
// 10. assign_version
server.tool(
"assign_version",
"Assign a committed version to an investor (determines what pitch data they see). Pass null to reset to default base tables.",
{
investor_id: z.string().uuid().describe("Investor UUID"),
version_id: z
.string()
.uuid()
.nullable()
.describe("Committed version UUID to assign, or null for default"),
},
async ({ investor_id, version_id }) => {
const data = await api("PATCH", `/api/admin/investors/${investor_id}`, {
assigned_version_id: version_id,
});
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
}
);
// 11. invite_investor
server.tool(
"invite_investor",
"Invite a new investor by email — sends a magic link for passwordless access to the pitch deck",
{
email: z.string().email().describe("Investor email address"),
name: z.string().optional().describe("Investor name"),
company: z.string().optional().describe("Investor company"),
},
async ({ email, name, company }) => {
const data = await api("POST", "/api/admin/invite", {
email,
name,
company,
});
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
}
);
// --- Start ---
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch((err) => {
console.error("MCP server error:", err);
process.exit(1);
});
+14
View File
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "dist",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"declaration": true
},
"include": ["src"]
}
+19 -1
View File
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'
import { jwtVerify } from 'jose'
import { jwtVerify } from 'jose/jwt/verify'
// Paths that bypass auth entirely
const PUBLIC_PATHS = [
@@ -12,6 +12,8 @@ const PUBLIC_PATHS = [
'/manifest.json',
'/sw.js',
'/icons',
'/screenshots', // SDK demo screenshots: public marketing assets. Must bypass auth because the next/image optimizer fetches them server-side without investor cookies.
'/team', // Team photos: must bypass auth for next/image optimizer
'/favicon.ico',
]
@@ -32,6 +34,11 @@ export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl
const secret = process.env.PITCH_JWT_SECRET
// Skip all auth in local dev when no secret is configured
if (!secret && process.env.NODE_ENV === 'development') {
return NextResponse.next()
}
// Allow public paths
if (isPublicPath(pathname)) {
return NextResponse.next()
@@ -67,6 +74,17 @@ export async function middleware(request: NextRequest) {
}
}
// ----- Allow admins to access investor routes (e.g. /api/chat in preview) -----
const adminFallback = request.cookies.get('pitch_admin_session')?.value
if (adminFallback && secret) {
try {
await jwtVerify(adminFallback, new TextEncoder().encode(secret), { audience: ADMIN_AUDIENCE })
return NextResponse.next()
} catch {
// Invalid admin token, fall through to investor auth
}
}
// ----- Investor-gated routes (everything else) -----
const token = request.cookies.get('pitch_session')?.value
+10
View File
@@ -1,10 +1,14 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
env: {
NEXT_PUBLIC_GIT_SHA: process.env.GIT_SHA || 'dev',
},
reactStrictMode: true,
typescript: {
ignoreBuildErrors: true,
},
serverExternalPackages: ['nodemailer'],
async headers() {
return [
{
@@ -23,3 +27,9 @@ const nextConfig = {
}
module.exports = nextConfig
// build trigger 1776412414
// build trigger 1776412427
// 1776414211
// 1776613263
// 1776674571
// 1776700446
+8 -1
View File
@@ -17,7 +17,8 @@
"pg": "^8.13.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"recharts": "^2.15.0"
"recharts": "^2.15.0",
"server-only": "^0.0.1"
},
"devDependencies": {
"@types/bcryptjs": "^2.4.6",
@@ -3743,6 +3744,12 @@
"node": ">=10"
}
},
"node_modules/server-only": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz",
"integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==",
"license": "MIT"
},
"node_modules/sharp": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",

Some files were not shown because too many files have changed in this diff Show More