diff --git a/pitch-deck/app/pitch-print/[versionId]/_components/PrintAnnexSlides.tsx b/pitch-deck/app/pitch-print/[versionId]/_components/PrintAnnexSlides.tsx index 999e726..ecaca0c 100644 --- a/pitch-deck/app/pitch-print/[versionId]/_components/PrintAnnexSlides.tsx +++ b/pitch-deck/app/pitch-print/[versionId]/_components/PrintAnnexSlides.tsx @@ -1,5 +1,6 @@ import { Language } from '@/lib/types' import { Page, COLORS, Callout, DataTable, ThreeCol, Bullets } from './PrintLayout' +import { ArchitectureDiagram, PipelineFlow } from './PrintDiagrams' interface SlideBase { lang: Language; pageNum: number; totalPages: number; versionName: string } @@ -158,68 +159,31 @@ export function PrintRegulatoryPage({ lang, pageNum, totalPages, versionName }: export function PrintArchitecturePage({ lang, pageNum, totalPages, versionName }: SlideBase) { const de = lang === 'de' return ( - - -
- {/* PRODUCT TIER */} -
-
{de ? 'Produkt-Schicht' : 'Product Tier'}
-
- {[ - { t: 'CERTifAI', s: de ? 'GenAI Mandantenportal' : 'GenAI Tenant Portal', tech: 'Rust · Dioxus · MongoDB · Keycloak · SearXNG · LangGraph', services: ['LiteLLM Dashboard', 'LibreChat + SSO', 'LangGraph Agents', 'MCP Hub'] }, - { t: 'COMPLAI', s: de ? 'Compliance & Audit' : 'Compliance & Audit', tech: 'Next.js 15 · FastAPI · Go/Gin · PostgreSQL · Qdrant · Valkey', services: [de ? 'DSGVO/AI Act/NIS2 (25k+ Controls)' : 'GDPR/AI Act/NIS2 (25k+ controls)', de ? 'RAG (380+ Quellen)' : 'RAG (380+ sources)', de ? 'Control Pipeline (LLM)' : 'Control pipeline (LLM)', 'MCP Client'] }, - { t: 'Compliance Scanner', s: de ? 'Code-Sicherheit' : 'Code Security', tech: 'Rust · Axum · MongoDB · Semgrep · Gitleaks · Syft', services: ['SAST · SBOM · CVE Pipeline', de ? 'KI-Triage (False Positives)' : 'AI Triage (false positives)', de ? 'KI-Pentest (autonom)' : 'AI Pentest (autonomous)', 'MCP Server'] }, - ].map((p, i) => ( -
-
{p.t}
-
{p.s}
-
{p.tech}
-
- {p.services.map((s, j) => ( -
0 ? `1px solid ${COLORS.slate100}` : 'none' }}>· {s}
- ))} -
-
- ))} -
-
- - {/* PROXY TIER */} -
-
{de ? 'Gateway-Schicht (KI-Proxy mit Guardrails)' : 'Gateway Tier (AI proxy with guardrails)'}
-
-
- LiteLLM Proxy - {de ? 'KI-Gateway · Bearer-Auth · Rate-Limiting · PII-Filter · Spend-Tracking' : 'AI gateway · bearer auth · rate limiting · PII filter · spend tracking'} -
-
-
· {de ? 'Token-Budget pro Mandant' : 'Per-tenant token budget'}
-
· {de ? 'PII-Guardrails alle Anfragen' : 'PII guardrails on all requests'}
-
· {de ? 'Anonyme EU-Web-Suche (SearXNG)' : 'Anonymous EU web search (SearXNG)'}
-
· {de ? 'Namespace-Isolierung pro API-Key' : 'Namespace isolation per API key'}
-
· {de ? 'Failover-Routing zwischen Modellen' : 'Failover routing between models'}
-
-
-
- - {/* INFERENCE TIER */} -
-
{de ? 'Inferenz-Schicht (lokal, air-gap-fähig)' : 'Inference Tier (local, air-gap capable)'}
-
- {[ - { t: de ? 'LLM Inferenz' : 'LLM Inference', tech: 'Qwen3-32B · Qwen3-Coder-30B · DeepSeek-R1-8B · Ollama', desc: de ? 'Vollständig lokal, air-gap fähig, GPU-optimiert. Daten verlassen nie den Server.' : 'Fully local, air-gap capable, GPU-optimized. Data never leaves the server.' }, - { t: 'Embeddings', tech: 'bge-m3 · Qdrant Vector DB · Sentence-Transformers', desc: de ? 'RAG mit 380+ Rechtsquellen indexiert, multilinguale Einbettungen, lokal.' : 'RAG with 380+ legal sources indexed, multilingual embeddings, local.' }, - { t: de ? 'KI-Tools' : 'AI Tools', tech: 'SearXNG · MCP Protocol · Semgrep API · Gitleaks API', desc: de ? 'Anonymisierte EU-Web-Suche, MCP-Integration für Audit-Dokumente und Code-Findings.' : 'Anonymized EU web search, MCP integration for audit docs and code findings.' }, - ].map((p, i) => ( -
-
{p.t}
-
{p.tech}
-
{p.desc}
-
- ))} -
-
-
+ + ) } @@ -318,23 +282,14 @@ export function PrintAIPipelinePage({ lang, pageNum, totalPages, versionName }: {/* Pipeline flow */}
{de ? 'Pipeline-Fluss' : 'Pipeline flow'}
-
- {[ + 99% precision' }, - ].map((s, i) => ( -
-
- {s.n} - {s.kpi} -
-
{s.t}
-
{s.d}
-
- ))} -
+ ]} + />
{/* Agent system */} diff --git a/pitch-deck/app/pitch-print/[versionId]/_components/PrintCharts.tsx b/pitch-deck/app/pitch-print/[versionId]/_components/PrintCharts.tsx new file mode 100644 index 0000000..0bd18bf --- /dev/null +++ b/pitch-deck/app/pitch-print/[versionId]/_components/PrintCharts.tsx @@ -0,0 +1,267 @@ +import React from 'react' +import { COLORS } from './PrintLayout' + +/* ====================================================================== */ +/* CHARTS */ +/* ====================================================================== */ + +interface BarSeries { + label: string + value: number + /** Optional secondary label rendered above the bar value (e.g. "Mio."). */ + unit?: string + tone?: 'default' | 'positive' | 'negative' | 'accent' +} + +export function BarChart({ + data, height = 36, maxOverride, formatValue, title, yAxisHint, +}: { + data: BarSeries[] + height?: number // mm + maxOverride?: number + formatValue?: (n: number) => string + title?: string + yAxisHint?: string +}) { + const max = maxOverride ?? Math.max(...data.map(d => d.value), 1) + const fmt = formatValue ?? ((n: number) => n.toLocaleString('de-DE')) + const ticks = [0, 0.25, 0.5, 0.75, 1].map(t => Math.round(max * t)) + + return ( +
+ {(title || yAxisHint) && ( +
+ {title &&
{title}
} + {yAxisHint &&
{yAxisHint}
} +
+ )} +
+ {/* Y-axis ticks */} +
+ {ticks.slice().reverse().map((t, i) => ( +
{fmt(t)}
+ ))} +
+ {/* Bars + grid */} +
+ {/* Grid lines */} + {ticks.slice(1).map((_, i) => ( +
+ ))} + {/* Bars */} +
+ {data.map((d, i) => { + const h = (d.value / max) * 100 + const color = d.tone === 'positive' ? COLORS.emerald600 + : d.tone === 'negative' ? COLORS.red600 + : d.tone === 'accent' ? COLORS.amber600 + : COLORS.indigo600 + return ( +
+
{fmt(d.value)}
+
+
+ ) + })} +
+
+
+ {/* X-axis labels */} +
+ {data.map((d, i) => ( +
{d.label}
+ ))} +
+
+ ) +} + +interface LinePoint { label: string; value: number } + +export function LineChart({ + data, height = 36, formatValue, color = COLORS.indigo600, title, fill = true, +}: { + data: LinePoint[] + height?: number + formatValue?: (n: number) => string + color?: string + title?: string + fill?: boolean +}) { + if (data.length < 2) return null + const max = Math.max(...data.map(d => d.value), 1) + const fmt = formatValue ?? ((n: number) => n.toLocaleString('de-DE')) + const w = 100 + const points = data.map((d, i) => ({ + x: (i / (data.length - 1)) * w, + y: 100 - (d.value / max) * 100, + v: d.value, + label: d.label, + })) + const pathD = points.map((p, i) => (i === 0 ? `M${p.x},${p.y}` : `L${p.x},${p.y}`)).join(' ') + const areaD = `${pathD} L100,100 L0,100 Z` + + return ( +
+ {title &&
{title}
} +
+ + {/* Grid */} + {[0.25, 0.5, 0.75].map(t => ( + + ))} + {fill && } + + {points.map((p, i) => ( + + ))} + + {/* Value labels above each point */} +
+ {points.map((p, i) => ( +
{fmt(p.v)}
+ ))} +
+
+ {/* X labels */} +
+ {points.map((p, i) => ( +
{p.label}
+ ))} +
+
+ ) +} + +/** Horizontal stacked-bar comparison (e.g. "you pay" vs "you save") */ +export function ComparisonBars({ + rows, formatValue, +}: { + rows: { label: string; bars: { tone: 'positive' | 'negative' | 'accent' | 'default'; value: number; cap?: string }[] }[] + formatValue?: (n: number) => string +}) { + const max = Math.max(...rows.flatMap(r => r.bars.map(b => b.value)), 1) + const fmt = formatValue ?? ((n: number) => n.toLocaleString('de-DE')) + return ( +
+ {rows.map((row, i) => ( +
+
{row.label}
+
+ {row.bars.map((b, j) => { + const w = (b.value / max) * 100 + const color = b.tone === 'positive' ? COLORS.emerald600 + : b.tone === 'negative' ? COLORS.red600 + : b.tone === 'accent' ? COLORS.amber600 + : COLORS.indigo600 + return ( +
+
+
+ {b.cap &&
{b.cap}
} +
+
{fmt(b.value)}
+
+ ) + })} +
+
+ ))} +
+ ) +} + +/** Donut chart for percentages (use-of-funds, equity, etc.) */ +export function DonutChart({ + segments, size = 32, thickness = 6, +}: { + segments: { label: string; pct: number; color: string }[] + size?: number // mm + thickness?: number // mm +}) { + const R = 50 + const r = R - (thickness / size) * 50 + let acc = 0 + const arcs = segments.map(s => { + const start = acc / 100 * Math.PI * 2 - Math.PI / 2 + acc += s.pct + const end = acc / 100 * Math.PI * 2 - Math.PI / 2 + const x1 = 50 + R * Math.cos(start), y1 = 50 + R * Math.sin(start) + const x2 = 50 + R * Math.cos(end), y2 = 50 + R * Math.sin(end) + const x3 = 50 + r * Math.cos(end), y3 = 50 + r * Math.sin(end) + const x4 = 50 + r * Math.cos(start), y4 = 50 + r * Math.sin(start) + const large = s.pct > 50 ? 1 : 0 + const d = `M${x1},${y1} A${R},${R} 0 ${large} 1 ${x2},${y2} L${x3},${y3} A${r},${r} 0 ${large} 0 ${x4},${y4} Z` + return { d, color: s.color, pct: s.pct, label: s.label } + }) + return ( + + {arcs.map((a, i) => ( + + ))} + + ) +} + +/** Progress meter (0-100%) — horizontal */ +export function ProgressBar({ pct, color = COLORS.indigo600, label, value }: { pct: number; color?: string; label?: string; value?: string }) { + return ( +
+ {(label || value) && ( +
+ {label && {label}} + {value && {value}} +
+ )} +
+
+
+
+ ) +} + +/** Nested market-size visual (TAM/SAM/SOM) */ +export function MarketFunnel({ + tam, sam, som, fmt, +}: { + tam: { value: number; label: string; growth?: number; note?: string } + sam: { value: number; label: string; growth?: number; note?: string } + som: { value: number; label: string; growth?: number; note?: string } + fmt: (v: number) => string +}) { + const samPct = sam.value / tam.value + const somPct = som.value / tam.value + return ( +
+ {/* TAM outer */} +
+
+ TAM · {tam.label} + {tam.growth != null && +{tam.growth}% p.a.} +
+
{fmt(tam.value)}
+ {tam.note &&
{tam.note}
} + + {/* SAM inner */} +
+
+ SAM · {sam.label} + {sam.growth != null && +{sam.growth}% p.a. · {Math.round(samPct * 100)}% TAM} +
+
{fmt(sam.value)}
+ {sam.note &&
{sam.note}
} + + {/* SOM inner-inner */} +
+
+ SOM · {som.label} + {som.growth != null && +{som.growth}% p.a. · {(somPct * 100).toFixed(1)}% TAM} +
+
{fmt(som.value)}
+ {som.note &&
{som.note}
} +
+
+
+
+ ) +} diff --git a/pitch-deck/app/pitch-print/[versionId]/_components/PrintDeck.tsx b/pitch-deck/app/pitch-print/[versionId]/_components/PrintDeck.tsx index 47609fb..8a15d6f 100644 --- a/pitch-deck/app/pitch-print/[versionId]/_components/PrintDeck.tsx +++ b/pitch-deck/app/pitch-print/[versionId]/_components/PrintDeck.tsx @@ -46,12 +46,13 @@ export default function PrintDeck({ pitchData, versionName, fmResults, fmAssumpt const hasFinancialDetail = financial && annualRows.length > 0 const de = lang === 'de' - // Base standard PDF: 28 pages (25 slides, 3 of them 2-page: exec-summary, usp, competition; finanzplan annex is 2 pages) - // 1 (exec1) + 1 (exec2) + 1 (cover) + 1 (problem) + 1 (solution) + 2 (usp) + 1 (regL) + 1 (product) + - // 1 (how) + 1 (market) + 1 (bm) + 1 (traction) + 2 (competition) + 1 (team) + 1 (ask) + 1 (savings) + - // 1 (strategy) + 2 (finanzplan) + 1 (assumptions) + 1 (regulatory) + 1 (architecture) + 1 (engineering) + - // 1 (aipipeline) + 1 (risks) + 1 (glossary) + 1 (disclaimer) = 28 - const BASE_PAGES = 28 + // Base standard PDF: 29 physical pages. + // 2 (exec) + 1 (cover) + 1 (problem) + 1 (solution) + 2 (usp) + 1 (regL) + + // 1 (product) + 1 (how) + 1 (market) + 1 (bm) + 1 (milestones) + 2 (competition) + + // 1 (team) + 1 (ask) + 1 (savings) + 1 (strategy) + 2 (finanzplan) + + // 1 (assumptions) + 1 (regulatory) + 1 (architecture) + 1 (engineering) + + // 1 (aipipeline) + 1 (risks) + 1 (glossary) + 1 (disclaimer) = 29 + const BASE_PAGES = 29 const totalPages = BASE_PAGES + (hasFinancialDetail ? 1 : 0) + (hasCapTable ? 1 : 0) useEffect(() => { diff --git a/pitch-deck/app/pitch-print/[versionId]/_components/PrintDiagrams.tsx b/pitch-deck/app/pitch-print/[versionId]/_components/PrintDiagrams.tsx new file mode 100644 index 0000000..cd22da9 --- /dev/null +++ b/pitch-deck/app/pitch-print/[versionId]/_components/PrintDiagrams.tsx @@ -0,0 +1,263 @@ +import React from 'react' +import { COLORS } from './PrintLayout' + +/* ====================================================================== */ +/* DIAGRAMS */ +/* ====================================================================== */ + +/** A single "node" in a flow / architecture diagram. */ +export function FlowNode({ + title, subtitle, items, accent = COLORS.indigo600, icon, kicker, footer, +}: { + title: string + subtitle?: string + items?: string[] + accent?: string + icon?: React.ReactNode + kicker?: string + footer?: string +}) { + return ( +
+ {kicker && ( +
{kicker}
+ )} +
+ {icon &&
{icon}
} +
+
{title}
+ {subtitle &&
{subtitle}
} +
+
+ {items && items.length > 0 && ( +
+ {items.map((it, i) => ( +
0 ? `1px solid ${COLORS.slate100}` : 'none', + lineHeight: 1.35, + }}>{it}
+ ))} +
+ )} + {footer && ( +
{footer}
+ )} +
+ ) +} + +/** Vertical down arrow (between rows of a flow diagram). */ +export function VArrow({ color = COLORS.slate400, label }: { color?: string; label?: string }) { + return ( +
+ + + + + {label &&
{label}
} +
+ ) +} + +/** Horizontal right arrow (between steps of a horizontal flow). */ +export function HArrow({ color = COLORS.slate400, label }: { color?: string; label?: string }) { + return ( +
+ + + + + {label &&
{label}
} +
+ ) +} + +/** Horizontal step strip with built-in arrows between items. */ +export function StepStrip({ + steps, accent = COLORS.indigo600, +}: { + steps: { n: string; t: string; d: string }[] + accent?: string +}) { + return ( +
+ {steps.map((s, i) => ( + +
+
+ {s.n} +
+
+
{s.t}
+
{s.d}
+
+ {i < steps.length - 1 && ( +
+ + + + +
+ )} + + ))} +
+ ) +} + +/** 3-tier architecture diagram (product → proxy → inference) */ +export function ArchitectureDiagram({ + product, proxy, inference, lang, +}: { + product: { kicker: string; title: string; subtitle: string; tech: string; services: string[] }[] + proxy: { title: string; subtitle: string; features: string[] } + inference: { title: string; subtitle: string; tech: string; desc: string }[] + lang: 'de' | 'en' +}) { + const de = lang === 'de' + return ( +
+ {/* PRODUCT TIER */} +
+
{de ? 'Produkt-Schicht' : 'Product Tier'}
+
+ {product.map((p, i) => ( + + ))} +
+
+ + {/* Down arrows between product and proxy */} +
+ {[0, 1, 2].map(i => )} +
+ + {/* PROXY */} +
+
+
+
+ {de ? 'Gateway' : 'Gateway'} + {proxy.title} + {proxy.subtitle} +
+
+
+ {proxy.features.map((f, i) => ( +
{f}
+ ))} +
+
+
+ + {/* Down arrows between proxy and inference */} +
+ {[0, 1, 2].map(i => )} +
+ + {/* INFERENCE TIER */} +
+
{de ? 'Inferenz-Schicht (lokal, air-gap-fähig)' : 'Inference Tier (local, air-gap capable)'}
+
+ {inference.map((p, i) => ( + + ))} +
+
+
+ ) +} + +/** Connected loop diagram for the USP "Compliance ↔ Code always in sync" closing card */ +export function LoopDiagram({ lang }: { lang: 'de' | 'en' }) { + const de = lang === 'de' + return ( + + + + + + + {/* Compliance box */} + + Compliance + {de ? 'Policies · Audits · VVT' : 'Policies · Audits · RoPA'} + + {/* Code box */} + + Code + {de ? 'Repos · CI/CD · Findings' : 'Repos · CI/CD · Findings'} + + {/* Top arrow: compliance → code */} + + {de ? 'Policies → Code (Real-time)' : 'Policies → Code (Real-time)'} + + {/* Bottom arrow: code → compliance */} + + {de ? 'Code-Δ → Evidence (Auto)' : 'Code-Δ → Evidence (Auto)'} + + ) +} + +/** 4-stage horizontal pipeline (e.g. RAG pipeline ingestion → ... → QA) */ +export function PipelineFlow({ + stages, accent = COLORS.indigo600, +}: { + stages: { n: string; t: string; d: string; kpi?: string }[] + accent?: string +}) { + return ( +
+ {stages.map((s, i) => ( + +
+
+
+ {s.n} + {s.kpi && {s.kpi}} +
+
{s.t}
+
{s.d}
+
+
+ {i < stages.length - 1 && ( +
+ + + + +
+ )} +
+ ))} +
+ ) +} diff --git a/pitch-deck/app/pitch-print/[versionId]/_components/PrintFinancialSlides.tsx b/pitch-deck/app/pitch-print/[versionId]/_components/PrintFinancialSlides.tsx index 572185c..20a0f1a 100644 --- a/pitch-deck/app/pitch-print/[versionId]/_components/PrintFinancialSlides.tsx +++ b/pitch-deck/app/pitch-print/[versionId]/_components/PrintFinancialSlides.tsx @@ -1,5 +1,6 @@ import { Language, FMResult, FMAssumption } from '@/lib/types' import { Page, COLORS, Callout, DataTable } from './PrintLayout' +import { BarChart, LineChart, DonutChart } from './PrintCharts' import { computeAnnualKPIs } from '@/lib/finanzplan/annual-kpis' interface SlideBase { lang: Language; pageNum: number; totalPages: number; versionName: string } @@ -140,91 +141,64 @@ export function PrintFinanzplanPage2({ fmResults, lang, pageNum, totalPages, ver } - const maxRev = Math.max(...kpis.map(k => k.totalRevenue), 1) - const maxEmp = Math.max(...kpis.map(k => k.employees), 1) + const fmtM = (n: number) => '€' + (n / 1e6).toFixed(1) + 'M' + const fmtK = (n: number) => '€' + (n / 1e3).toFixed(0) + 'k' + const pickFmt = (max: number) => max >= 1e6 ? fmtM : fmtK return ( - + - {/* KPI table */} -
+ {/* Compact KPI table */} +
({ header: String(k.year), numeric: true, width: '15.2%' as string })), + { header: de ? 'KPI' : 'KPI', width: '22%' }, + ...kpis.map(k => ({ header: String(k.year), numeric: true })), ]} rows={[ ['ARR (Dez)', ...kpis.map(k => fmtEur(k.arr))], - ['MRR (Dez)', ...kpis.map(k => fmtEur(k.mrr))], - [de ? 'ARPU (€/Monat)' : 'ARPU (€/mo)', ...kpis.map(k => fmtEur(k.arpu))], - [de ? 'Kunden' : 'Customers', ...kpis.map(k => k.customers.toLocaleString('de-DE'))], - [de ? 'Mitarbeiter' : 'Employees', ...kpis.map(k => k.employees.toLocaleString('de-DE'))], + [de ? 'MRR · ARPU' : 'MRR · ARPU', ...kpis.map(k => fmtEur(k.mrr) + ' · ' + fmtEur(k.arpu))], + [de ? 'Kunden · MA' : 'Customers · FTE', ...kpis.map(k => k.customers.toLocaleString('de-DE') + ' · ' + k.employees)], [de ? 'Umsatz / MA' : 'Revenue / FTE', ...kpis.map(k => fmtEur(k.revenuePerEmployee))], - [de ? 'Bruttomarge %' : 'Gross margin %', ...kpis.map(k => k.grossMargin + '%')], - [de ? 'EBIT' : 'EBIT', ...kpis.map(k => = 0 ? COLORS.emerald700 : COLORS.red700, fontWeight: 700 }}>{fmtEur(k.ebit)})], - [de ? 'EBIT-Marge' : 'EBIT margin', ...kpis.map(k => = 0 ? COLORS.emerald700 : COLORS.red700 }}>{k.ebitMargin}%)], - [de ? 'Steuern (~30%)' : 'Taxes (~30%)', ...kpis.map(k => fmtEur(k.taxes))], - [de ? 'Netto-Ergebnis' : 'Net income', ...kpis.map(k => = 0 ? COLORS.emerald700 : COLORS.red700 }}>{fmtEur(k.netIncome)})], - [de ? 'Burn-Rate (Dez)' : 'Burn rate (Dec)', ...kpis.map(k => fmtEur(k.burnRate))], - [de ? 'Runway (Monate)' : 'Runway (months)', ...kpis.map(k => k.runway == null ? '∞' : String(k.runway))], - [de ? 'Cash-Bestand (Dez)' : 'Cash balance (Dec)', ...kpis.map(k => fmtEur(k.cashBalance))], + [de ? 'Bruttomarge' : 'Gross margin', ...kpis.map(k => k.grossMargin + '%')], + [de ? 'EBIT · Marge' : 'EBIT · margin', ...kpis.map(k => = 0 ? COLORS.emerald700 : COLORS.red700, fontWeight: 700 }}>{fmtEur(k.ebit)} · {k.ebitMargin}%)], + [de ? 'Netto-Ergebnis' : 'Net income', ...kpis.map(k => = 0 ? COLORS.emerald700 : COLORS.red700 }}>{fmtEur(k.netIncome)})], + [de ? 'Burn · Runway' : 'Burn · runway', ...kpis.map(k => fmtEur(k.burnRate) + ' · ' + (k.runway == null ? '∞' : String(k.runway) + 'm'))], + [de ? 'Cash-Bestand' : 'Cash balance', ...kpis.map(k => fmtEur(k.cashBalance))], ]} highlightFirstCol />
- {/* Charts */} -
- {/* Revenue chart */} -
-
{de ? 'Umsatz-Wachstum (Mio. €)' : 'Revenue growth (€M)'}
-
- {kpis.map((k, i) => { - const h = (k.totalRevenue / maxRev) * 100 - return ( -
-
{(k.totalRevenue / 1e6).toFixed(1)}
-
-
- ) - })} -
-
- {kpis.map((k, i) => ( -
{k.year}
- ))} -
-
- - {/* Headcount + customers chart */} -
-
{de ? 'Mitarbeiter & Kunden' : 'Employees & customers'}
-
- {kpis.map((k, i) => { - const h = (k.employees / maxEmp) * 100 - return ( -
-
- {k.employees} -
- {k.customers}c -
-
-
- ) - })} -
-
- {kpis.map((k, i) => ( -
{k.year}
- ))} -
-
- {de ? 'Mitarbeiter (FTE)' : 'Employees (FTE)'} - {de ? 'Zahl unten: Kunden' : 'Number below: customers'} -
-
+ {/* Charts grid 2x2 */} +
+ ({ label: String(k.year), value: k.totalRevenue, tone: 'default' }))} + height={26} + formatValue={pickFmt(Math.max(...kpis.map(k => k.totalRevenue)))} + /> + ({ label: String(k.year), value: k.ebit, tone: k.ebit >= 0 ? 'positive' : 'negative' }))} + height={26} + formatValue={pickFmt(Math.max(...kpis.map(k => Math.abs(k.ebit))))} + /> + ({ label: String(k.year), value: Math.max(k.cashBalance, 0) }))} + height={26} + color={COLORS.indigo600} + formatValue={pickFmt(Math.max(...kpis.map(k => k.cashBalance)))} + fill + /> + ({ label: String(k.year), value: k.employees, tone: 'accent' }))} + height={26} + formatValue={(n) => String(Math.round(n))} + />
) @@ -341,18 +315,19 @@ export function PrintCapTablePage({ lang, pageNum, totalPages, versionName }: Sl
{de ? 'Anteilsverteilung Post Pre-Seed' : 'Share distribution post pre-seed'}
-
- {CAP_TABLE_DATA.map(d => ( -
- ))} -
- {CAP_TABLE_DATA.map(d => ( -
-
- {d.name} - {d.pct}% + +
+ ({ label: d.name, pct: d.pct, color: d.color }))} /> +
+ {CAP_TABLE_DATA.map(d => ( +
+
+ {d.name} + {d.pct}% +
+ ))}
- ))} +
diff --git a/pitch-deck/app/pitch-print/[versionId]/_components/PrintIntroSlides.tsx b/pitch-deck/app/pitch-print/[versionId]/_components/PrintIntroSlides.tsx index a132830..9b6a424 100644 --- a/pitch-deck/app/pitch-print/[versionId]/_components/PrintIntroSlides.tsx +++ b/pitch-deck/app/pitch-print/[versionId]/_components/PrintIntroSlides.tsx @@ -8,54 +8,95 @@ interface SlideBase { lang: Language; pageNum: number; totalPages: number; versi export function PrintCoverPage({ company, funding, lang, versionName }: { company: PitchCompany; funding: PitchFunding; lang: Language; versionName: string }) { const de = lang === 'de' const instrument = funding?.instrument || 'Pre-Seed' + const amount = funding?.amount_eur || 1_000_000 const tagline = de ? (company?.tagline_de || 'Kontinuierliche Compliance für europäische Unternehmen.') : (company?.tagline_en || 'Continuous compliance for European companies.') return (
-
- {/* TOP META */} -
- BreakPilot · Investor Brief - {versionName} -
+
- {/* HERO */} -
-

- {instrument} · Q4 2026 -

-

- {company?.name || 'BreakPilot'}. -

-

- {tagline} -

-
-

- {de - ? 'DSGVO-konforme KI-Plattform für kontinuierliche Code-Security und automatisierte Compliance. Souverän gehostet, integriert in europäische Workflows.' - : 'GDPR-compliant AI platform for continuous code security and automated compliance. Sovereign-hosted, integrated into European workflows.'} -

-
- - {/* META GRID */} -
- {([ - [de ? 'Gegründet' : 'Founded', company?.founding_date ? new Date(company.founding_date).getFullYear().toString() : 'Aug 2026'], - [de ? 'Standort' : 'HQ', company?.hq_city || 'Bodman-Ludwigshafen'], - [de ? 'Instrument' : 'Instrument', instrument], - [de ? 'Runde' : 'Round', funding?.round_name || 'Pre-Seed'], - ] as [string, string][]).map(([label, val]) => ( -
-

{label}

-

{val}

+ {/* LEFT INDIGO BLOCK */} +
+
+
+ {de ? 'Investor Brief' : 'Investor Brief'}
- ))} +
+
+ {de + ? 'DSGVO-konforme KI-Plattform für kontinuierliche Code-Security und automatisierte Compliance. Souverän gehostet, integriert in europäische Workflows.' + : 'GDPR-compliant AI platform for continuous code security and automated compliance. Sovereign-hosted, integrated into European workflows.'} +
+
+ + {/* Hero stat */} +
+
+ {de ? '25 000 + atomare Prüfaspekte' : '25 000 + atomic audit aspects'} +
+
+ {de ? '380 + Regularien · 10 Branchen' : '380 + regulations · 10 industries'} +
+
+ {de ? '500 K + Lines of Code · 45 Container' : '500 K + lines of code · 45 containers'} +
+
+ + {/* Bottom: version + confidential */} +
+
{versionName}
+
{de ? 'Vertraulich · Nur Investoren' : 'Confidential · Investors only'}
+
- {/* FOOTER */} -
- {de ? 'Vertraulich, Nur für Investoren' : 'Confidential, For Investor Use Only'} - CONFIDENTIAL + {/* RIGHT WHITE PANE */} +
+
+
+ {instrument} · Q4 2026 +
+

+ {company?.name || 'BreakPilot'}. +

+
+

+ {tagline} +

+
+ + {/* Key terms */} +
+
+ {de ? 'Key Terms' : 'Key terms'} +
+
+ {([ + [de ? 'Funding' : 'Funding', '€' + (amount / 1_000_000).toFixed(1) + 'M'], + [de ? 'Pre-Money' : 'Pre-money', '€4.0M'], + [de ? 'Instrument' : 'Instrument', instrument], + [de ? 'Standort' : 'HQ', company?.hq_city || 'Bodman'], + ] as [string, string][]).map(([label, val]) => ( +
+
{label}
+
{val}
+
+ ))} +
+
+ {de + ? 'Gründerteam Benjamin Bönisch (CEO) und Sharang Parnerkar (CTO). Markeneintragung DPMA · EUIPO-Anmeldung in Bearbeitung · GmbH-Gründung August 2026.' + : 'Founding team Benjamin Bönisch (CEO) and Sharang Parnerkar (CTO). Trademark DPMA registered · EUIPO filing in progress · GmbH incorporation August 2026.'} +
+
diff --git a/pitch-deck/app/pitch-print/[versionId]/_components/PrintMarketSlides.tsx b/pitch-deck/app/pitch-print/[versionId]/_components/PrintMarketSlides.tsx index 6d121d6..e368173 100644 --- a/pitch-deck/app/pitch-print/[versionId]/_components/PrintMarketSlides.tsx +++ b/pitch-deck/app/pitch-print/[versionId]/_components/PrintMarketSlides.tsx @@ -1,5 +1,6 @@ import { Language, PitchMarket, PitchTeamMember, PitchMilestone, PitchFunding } from '@/lib/types' -import { Page, TwoCol, Bullets, Callout, COLORS, DataTable, StatLine } from './PrintLayout' +import { Page, Callout, COLORS, DataTable, StatLine } from './PrintLayout' +import { MarketFunnel, ComparisonBars, DonutChart } from './PrintCharts' interface SlideBase { lang: Language; pageNum: number; totalPages: number; versionName: string } @@ -21,49 +22,19 @@ export function PrintMarketPage({ market, lang, pageNum, totalPages, versionName return ( $1,1 Mrd. ARR. Kein Anbieter bedient den Maschinenbau spezifisch.' : 'Validated market: top-10 compliance vendors generate >$1.1B ARR. No vendor specifically serves manufacturing.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName} footnote={de ? 'Sacra · Bitkom Cloud Monitor 2024 · DIHK 2024 · VDMA · Statista' : 'Sacra · Bitkom Cloud Monitor 2024 · DIHK 2024 · VDMA · Statista'}> - {/* TAM/SAM/SOM as nested rectangles */} -
+
{de ? 'Marktdimensionierung' : 'Market sizing'}
- - {/* TAM */} - {tam && ( -
-
- TAM · {de ? 'Total Addressable Market' : 'Total Addressable Market'} - +{tam.growth_rate_pct ?? 14}% p.a. -
-
{fmtEur(tam.value_eur, de)}
-
{de ? 'Globaler Compliance- und GRC-Markt (alle Branchen, alle Größen).' : 'Global compliance and GRC market (all industries, all sizes).'}
-
- )} - - {/* SAM */} - {sam && ( -
-
- SAM · {de ? 'Serviceable Addressable' : 'Serviceable Addressable'} - +{sam.growth_rate_pct ?? 18}% p.a. -
-
{fmtEur(sam.value_eur, de)}
-
{de ? 'DACH + EU: regulierte Branchen, KMU + Enterprise.' : 'DACH + EU: regulated industries, SMB + enterprise.'}
-
- )} - - {/* SOM */} - {som && ( -
-
- SOM · {de ? 'Kernmarkt 5 Jahre' : 'Core market 5 yrs'} - +{som.growth_rate_pct ?? 25}% p.a. -
-
{fmtEur(som.value_eur, de)}
-
{de ? 'Anlagen- und Maschinenbau DACH, unser Kernsegment.' : 'Machine & plant manufacturing DACH, our core segment.'}
-
+ {tam && sam && som && ( + fmtEur(v, de)} + /> )}
- {/* Segment context */}
{de ? 'Kernsegment: Maschinen- und Anlagenbau DACH' : 'Core segment: Machine & plant manufacturing DACH'}
- -
- CO-FOUNDER · 01 / 02 - Equity {members[0]?.equity_pct ?? 37.3}% -
-
{members[0]?.name || 'Benjamin Bönisch'}
-
{de ? (members[0]?.role_de || 'CEO & Co-Founder') : (members[0]?.role_en || 'CEO & Co-Founder')}
-
{de ? (members[0]?.bio_de) : (members[0]?.bio_en)}
-
-
{de ? 'Expertise' : 'Expertise'}
-
- {(members[0]?.expertise || []).map((e, i) => ( - {e} - ))} +
+ {[0, 1].map(idx => { + const m = members[idx] + if (!m) return null + return ( +
+
+ {/* Photo */} +
+ {m.photo_url ? ( + /* eslint-disable-next-line @next/next/no-img-element */ + {m.name} + ) : ( +
+ {m.name?.split(' ').map(s => s[0]).slice(0, 2).join('') || '?'} +
+ )} +
+ {/* Header text */} +
+
+ CO-FOUNDER · {String(idx + 1).padStart(2, '0')} / 02 + {(m.equity_pct ?? 37.3).toLocaleString('de-DE')}% Equity +
+
{m.name}
+
{de ? m.role_de : m.role_en}
+
+
+
{de ? m.bio_de : m.bio_en}
+
+
{de ? 'Expertise' : 'Expertise'}
+
+ {(m.expertise || []).map((e, i) => ( + {e} + ))} +
+
-
-
- } right={ -
-
- CO-FOUNDER · 02 / 02 - Equity {members[1]?.equity_pct ?? 37.3}% -
-
{members[1]?.name || 'Sharang Parnerkar'}
-
{de ? (members[1]?.role_de || 'CTO & Co-Founder') : (members[1]?.role_en || 'CTO & Co-Founder')}
-
{de ? (members[1]?.bio_de) : (members[1]?.bio_en)}
-
-
{de ? 'Expertise' : 'Expertise'}
-
- {(members[1]?.expertise || []).map((e, i) => ( - {e} - ))} -
-
-
- } /> + ) + })} +
@@ -292,27 +267,31 @@ export function PrintTheAskPage({ funding, lang, pageNum, totalPages, versionNam {/* Use of funds */}
-
{de ? 'Use of Funds' : 'Use of Funds'}
- {/* Horizontal bar */} -
- {useOfFunds.map((u, i) => { - const colors = [COLORS.indigo600, COLORS.indigo500, COLORS.amber600, COLORS.slate600, COLORS.slate400] - return ( -
- ) - })} -
- {useOfFunds.map((u, i) => { - const colors = [COLORS.indigo600, COLORS.indigo500, COLORS.amber600, COLORS.slate600, COLORS.slate400] +
{de ? 'Use of Funds' : 'Use of funds'}
+ {(() => { + const palette = [COLORS.indigo600, COLORS.indigo500, COLORS.amber600, COLORS.slate600, COLORS.slate400] return ( -
-
-
{de ? u.label_de : u.label_en}
-
{u.percentage}%
-
€{(amount * u.percentage / 100 / 1000).toFixed(0)}k
+
+
+ ({ label: de ? u.label_de : u.label_en, pct: u.percentage, color: palette[i % palette.length] }))} + /> +
+
+ {useOfFunds.map((u, i) => ( +
+
+
{de ? u.label_de : u.label_en}
+
{u.percentage}%
+
€{(amount * u.percentage / 100 / 1000).toFixed(0)}k
+
+ ))} +
) - })} + })()}
@@ -323,68 +302,70 @@ export function PrintTheAskPage({ funding, lang, pageNum, totalPages, versionNam export function PrintCustomerSavingsPage({ lang, pageNum, totalPages, versionName }: SlideBase) { const de = lang === 'de' + const items = [ + { l: de ? 'Pentests' : 'Pentests', today: 15000, bp: 13000 }, + { l: de ? 'CE-SW-Risiko' : 'CE software risk', today: 12000, bp: 9000 }, + { l: de ? 'Compliance-Zeit' : 'Compliance time', today: 18000, bp: 15000 }, + { l: de ? 'Audit-Vorber.' : 'Audit prep', today: 9000, bp: 9000 }, + { l: de ? 'Legal (DSGVO/AI Act)' : 'Legal (GDPR/AI Act)', today: 8000, bp: 5000 }, + { l: de ? 'Auditmanager-Software' : 'Audit manager SW', today: 5000, bp: 0 }, + { l: de ? 'Schulungen extern' : 'External training', today: 4000, bp: 4000 }, + ] + const totalToday = items.reduce((s, i) => s + i.today, 0) + const totalSaved = items.reduce((s, i) => s + i.bp, 0) + const totalBpCost = 25000 + return ( - + -
-
-
{de ? 'Was der Kunde heute bezahlt (ohne BreakPilot)' : 'What customer pays today (without BreakPilot)'}
- {de ? 'Summe heute' : 'Today total'}, €71.000, ''], - ]} - dense - /> - -
- - {de - ? 'Zeit der GF + Compliance-Beauftragten (~30 Tage/Jahr), DSGVO-Bußgelder bei Fehlern (bis zu 4% Jahresumsatz), verlorene RFQs durch fehlende Compliance-Nachweise.' - : 'Time of management + compliance officer (~30 days/year), GDPR fines on errors (up to 4% annual revenue), lost RFQs due to missing compliance evidence.'} - + {/* Big stat header */} +
+ {[ + { l: de ? 'Heute (ohne BP)' : 'Today (without BP)', v: '€' + (totalToday / 1000).toFixed(0) + 'k', tone: COLORS.red700 }, + { l: de ? 'BreakPilot Pro / Jahr' : 'BreakPilot Pro / year', v: '€' + (totalBpCost / 1000).toFixed(0) + 'k', tone: COLORS.indigo600 }, + { l: de ? 'Ersparnis / KMU' : 'Savings / SME', v: '€' + (totalSaved / 1000).toFixed(0) + 'k', tone: COLORS.emerald700 }, + { l: de ? 'Netto-Effekt' : 'Net effect', v: '+€' + ((totalSaved - totalBpCost) / 1000).toFixed(0) + 'k', tone: COLORS.emerald700 }, + ].map((k, i) => ( +
+
{k.v}
+
{k.l}
+ ))} +
+ + {/* Bar comparison */} +
+
+
{de ? 'Heute vs. mit BreakPilot (€/Jahr/KMU)' : 'Today vs. with BreakPilot (€/yr/SME)'}
+ ({ + label: it.l, + bars: [ + { tone: 'negative', value: it.today, cap: de ? 'Heute' : 'Today' }, + { tone: 'positive', value: it.bp, cap: de ? 'gespart mit BP' : 'saved with BP' }, + ], + }))} + formatValue={(n) => '€' + (n / 1000).toFixed(0) + 'k'} + />
-
{de ? 'Mit BreakPilot' : 'With BreakPilot'}
- {de ? 'BreakPilot Pro' : 'BreakPilot Pro'}, €25.000, ''], - [{de ? 'Gesamt-Ersparnis' : 'Total savings'}, '', €55.000], - ]} - dense - /> +
{de ? 'Ersparnis-Aufschlüsselung' : 'Savings breakdown'}
+
+ + + + + + +
-
-
{de ? 'Netto-Effekt Jahr 1' : 'Net effect year 1'}
-
-
+€30k
-
{de ? 'pro KMU / Jahr' : 'per SME / year'}
-
-
{de ? 'Kunde spart €55k, zahlt €25k. ROI ab Tag 1. Zusätzlich: keine Bußgelder, RFQ-Win-Rate ↑, Schlaf-Frieden für die GF.' : 'Customer saves €55k, pays €25k. ROI from day 1. Plus: no fines, RFQ win-rate ↑, peace of mind for management.'}
+
+ + {de + ? 'Zeit der GF + Compliance-Beauftragten (~30 Tage/Jahr), DSGVO-Bußgelder (bis 4% Jahresumsatz), verlorene RFQs durch fehlende Compliance-Nachweise. Nicht in obigen Zahlen enthalten.' + : 'Time of management + compliance officer (~30 days/year), GDPR fines (up to 4% annual revenue), lost RFQs from missing compliance evidence. Not included in numbers above.'} +
diff --git a/pitch-deck/app/pitch-print/[versionId]/_components/PrintProductSlides.tsx b/pitch-deck/app/pitch-print/[versionId]/_components/PrintProductSlides.tsx index 229f9d9..07b7282 100644 --- a/pitch-deck/app/pitch-print/[versionId]/_components/PrintProductSlides.tsx +++ b/pitch-deck/app/pitch-print/[versionId]/_components/PrintProductSlides.tsx @@ -1,9 +1,21 @@ import { Language, PitchProduct } from '@/lib/types' -import { Page, TwoCol, ThreeCol, FourCol, Bullets, Callout, COLORS, Divider, DataTable, StatLine } from './PrintLayout' +import { Page, Bullets, Callout, COLORS, DataTable, StatLine } from './PrintLayout' +import { StepStrip, LoopDiagram } from './PrintDiagrams' import { getDetails } from '@/components/slides/USPSlide.data' +import { + ScanLine, ShieldCheck, FileText, ClipboardCheck, Users, UserCheck, + AlertTriangle, Brain, Target, GraduationCap, TrendingUp, MessageSquare, + Shield, Layers, Globe, FileSearch, Sparkles, Repeat, ArrowLeftRight, Infinity, + type LucideIcon, +} from 'lucide-react' interface SlideBase { lang: Language; pageNum: number; totalPages: number; versionName: string } +const USP_ICON: Record = { + rfq: FileSearch, process: ClipboardCheck, bidir: ArrowLeftRight, cont: Repeat, + trace: Layers, engine: Sparkles, opt: TrendingUp, stack: Globe, hub: Infinity, +} + /* ===== USP, PAGE 1 (4 pillars) ===== */ export function PrintUSPPage1({ lang, pageNum, totalPages, versionName }: SlideBase) { @@ -17,19 +29,25 @@ export function PrintUSPPage1({ lang, pageNum, totalPages, versionName }: SlideB
{pillars.map((k, i) => { const p = d[k] + const Icon = USP_ICON[k] return ( -
-
- {p.kicker} - {String(i + 1).padStart(2, '0')} / 04 +
+
+
+ {Icon && } +
+
+
{p.kicker}
+
{String(i + 1).padStart(2, '0')} / 04
+
-
{p.title}
+
{p.title}
{p.body}
{p.bullets && } {p.stat && (
{p.stat.k} - {p.stat.v} + {p.stat.v}
)}
@@ -50,28 +68,32 @@ export function PrintUSPPage2({ lang, pageNum, totalPages, versionName }: SlideB return ( -
+
{cards.map(k => { const p = d[k] + const Icon = USP_ICON[k] return (
-
{p.kicker}
-
{p.title}
-
{p.body}
+
+ {Icon && } + {p.kicker} +
+
{p.title}
+
{p.body}
{p.bullets && }
) })}
-
- -
{d.hub.title}
-
{d.hub.body}
- {d.hub.bullets && ( -
- )} -
+
+
+ + {de ? 'Die Schleife' : 'The Loop'} +
+
{d.hub.title}
+
{d.hub.body}
+
) @@ -168,33 +190,37 @@ export function PrintRegulatoryLandscapePage({ lang, pageNum, totalPages, versio /* ===== PRODUCT / MODULAR TOOLKIT ===== */ +const MODULE_ICONS: LucideIcon[] = [ + ScanLine, ShieldCheck, FileText, ClipboardCheck, Users, UserCheck, + AlertTriangle, Brain, Target, GraduationCap, TrendingUp, MessageSquare, +] const MODULES_FULL_DE = [ - { name: 'Code Security', icon: '◇', desc: 'SAST · DAST · SBOM · Container · Secrets · Pentesting', features: ['Bei jedem Push', 'Auto-Fix LLM', 'CI/CD-integriert'] }, - { name: 'CE-SW-Risikobeurteilung', icon: '◇', desc: 'CE-Kennzeichnung für Maschinen mit Software-Anteil', features: ['Maschinen-VO', 'CRA-konform', 'Code-Basis-Analyse'] }, - { name: 'Compliance-Dokumente', icon: '◇', desc: 'VVT (Art. 30) · TOMs · DSFA (Art. 35) · Löschkonzept', features: ['Auto-Generiert', 'Versionsverlauf', 'Audit-tauglich'] }, - { name: 'Audit Manager', icon: '◇', desc: 'Abweichungen End-to-End: Rollen · Stichtage · Eskalation', features: ['Tickets + Nachweise', 'GF-Eskalation', 'Compliance-SLA'] }, - { name: 'DSR / Betroffenenrechte', icon: '◇', desc: 'Auskunft, Berichtigung, Löschung, Datenübertragbarkeit', features: ['Self-Service', 'Identitätsprüfung', 'Frist-Tracking'] }, - { name: 'Consent', icon: '◇', desc: 'Einwilligungs-Management, Cookie-Banner, ePrivacy', features: ['CMP integriert', 'Audit-Log', 'Multi-Tenant'] }, - { name: 'Incident Response', icon: '◇', desc: 'Vorfälle, Meldung (72h), Mitigation, Forensik', features: ['Art. 33/34 DSGVO', 'BSI-Meldepfade', 'Forensik-Hooks'] }, - { name: 'Compliance LLM', icon: '◇', desc: 'GPT für Text + Audio, EU-gehostet, mit Quellenangabe', features: ['Self-Hosted', 'EU-souverän', 'Audit-zitierbar'] }, - { name: 'Tender Matching', icon: '◇', desc: 'RFQ-Antworten automatisch gegen Codebase + Policies', features: ['Stunden statt Wochen', 'Win-ready', 'Klausel-Mapping'] }, - { name: 'Academy', icon: '◇', desc: 'Online-Schulungen für Geschäftsführung und Mitarbeiter', features: ['Mandatory Training', 'Zertifikate', 'GF-Pflicht erfüllt'] }, - { name: 'Compliance Optimizer', icon: '◇', desc: 'Maximale KI-Nutzung im legalen Rahmen, ersetzt 20-200k € Anwaltskosten', features: ['ROI-ranking', 'Sweet-Spot', 'Risikobalance'] }, - { name: 'Kommunikation', icon: '◇', desc: 'Chat (Matrix) + Video (Jitsi) + KI-Support', features: ['Self-Hosted', 'EU-Hosting', 'Audit-Logs'] }, + { name: 'Code Security', desc: 'SAST · DAST · SBOM · Container · Secrets · Pentesting', features: ['Bei jedem Push', 'Auto-Fix LLM', 'CI/CD-integriert'] }, + { name: 'CE-SW-Risikobeurteilung', desc: 'CE-Kennzeichnung für Maschinen mit Software-Anteil', features: ['Maschinen-VO', 'CRA-konform', 'Code-Basis-Analyse'] }, + { name: 'Compliance-Dokumente', desc: 'VVT (Art. 30) · TOMs · DSFA (Art. 35) · Löschkonzept', features: ['Auto-Generiert', 'Versionsverlauf', 'Audit-tauglich'] }, + { name: 'Audit Manager', desc: 'Abweichungen End-to-End: Rollen · Stichtage · Eskalation', features: ['Tickets + Nachweise', 'GF-Eskalation', 'Compliance-SLA'] }, + { name: 'DSR / Betroffenenrechte', desc: 'Auskunft, Berichtigung, Löschung, Datenübertragbarkeit', features: ['Self-Service', 'Identitätsprüfung', 'Frist-Tracking'] }, + { name: 'Consent', desc: 'Einwilligungs-Management, Cookie-Banner, ePrivacy', features: ['CMP integriert', 'Audit-Log', 'Multi-Tenant'] }, + { name: 'Incident Response', desc: 'Vorfälle, Meldung (72h), Mitigation, Forensik', features: ['Art. 33/34 DSGVO', 'BSI-Meldepfade', 'Forensik-Hooks'] }, + { name: 'Compliance LLM', desc: 'GPT für Text + Audio, EU-gehostet, mit Quellenangabe', features: ['Self-Hosted', 'EU-souverän', 'Audit-zitierbar'] }, + { name: 'Tender Matching', desc: 'RFQ-Antworten automatisch gegen Codebase + Policies', features: ['Stunden statt Wochen', 'Win-ready', 'Klausel-Mapping'] }, + { name: 'Academy', desc: 'Online-Schulungen für Geschäftsführung und Mitarbeiter', features: ['Mandatory Training', 'Zertifikate', 'GF-Pflicht erfüllt'] }, + { name: 'Compliance Optimizer', desc: 'Maximale KI-Nutzung im legalen Rahmen, ersetzt 20-200k € Anwaltskosten', features: ['ROI-ranking', 'Sweet-Spot', 'Risikobalance'] }, + { name: 'Kommunikation', desc: 'Chat (Matrix) + Video (Jitsi) + KI-Support', features: ['Self-Hosted', 'EU-Hosting', 'Audit-Logs'] }, ] const MODULES_FULL_EN = [ - { name: 'Code Security', icon: '◇', desc: 'SAST · DAST · SBOM · Container · Secrets · Pentesting', features: ['Every push', 'Auto-fix LLM', 'CI/CD integrated'] }, - { name: 'CE SW Risk Assessment', icon: '◇', desc: 'CE marking for machinery with software', features: ['Machinery Reg.', 'CRA-compliant', 'Code-level analysis'] }, - { name: 'Compliance Documents', icon: '◇', desc: 'RoPA (Art. 30) · TOMs · DPIA (Art. 35) · Retention', features: ['Auto-generated', 'Version history', 'Audit-ready'] }, - { name: 'Audit Manager', icon: '◇', desc: 'Deviations end-to-end: roles · deadlines · escalation', features: ['Tickets + evidence', 'Mgmt escalation', 'Compliance SLA'] }, - { name: 'DSR / Data Subject Rights', icon: '◇', desc: 'Access, rectification, erasure, portability', features: ['Self-service', 'Identity check', 'Deadline tracking'] }, - { name: 'Consent', icon: '◇', desc: 'Consent mgmt, cookie banner, ePrivacy', features: ['CMP integrated', 'Audit log', 'Multi-tenant'] }, - { name: 'Incident Response', icon: '◇', desc: 'Breaches, reporting (72h), mitigation, forensics', features: ['GDPR Art. 33/34', 'BSI channels', 'Forensic hooks'] }, - { name: 'Compliance LLM', icon: '◇', desc: 'GPT for text + audio, EU-hosted, with citations', features: ['Self-hosted', 'EU-sovereign', 'Audit-citable'] }, - { name: 'Tender Matching', icon: '◇', desc: 'RFQ answers automatically against codebase + policies', features: ['Hours not weeks', 'Win-ready', 'Clause mapping'] }, - { name: 'Academy', icon: '◇', desc: 'Online training for management and staff', features: ['Mandatory training', 'Certificates', 'Mgmt duties fulfilled'] }, - { name: 'Compliance Optimizer', icon: '◇', desc: 'Max AI use within legal limits, replaces €20-200k legal fees', features: ['ROI ranking', 'Sweet spot', 'Risk balance'] }, - { name: 'Communication', icon: '◇', desc: 'Chat (Matrix) + video (Jitsi) + AI support', features: ['Self-hosted', 'EU hosting', 'Audit logs'] }, + { name: 'Code Security', desc: 'SAST · DAST · SBOM · Container · Secrets · Pentesting', features: ['Every push', 'Auto-fix LLM', 'CI/CD integrated'] }, + { name: 'CE SW Risk Assessment', desc: 'CE marking for machinery with software', features: ['Machinery Reg.', 'CRA-compliant', 'Code-level analysis'] }, + { name: 'Compliance Documents', desc: 'RoPA (Art. 30) · TOMs · DPIA (Art. 35) · Retention', features: ['Auto-generated', 'Version history', 'Audit-ready'] }, + { name: 'Audit Manager', desc: 'Deviations end-to-end: roles · deadlines · escalation', features: ['Tickets + evidence', 'Mgmt escalation', 'Compliance SLA'] }, + { name: 'DSR / Data Subject Rights', desc: 'Access, rectification, erasure, portability', features: ['Self-service', 'Identity check', 'Deadline tracking'] }, + { name: 'Consent', desc: 'Consent mgmt, cookie banner, ePrivacy', features: ['CMP integrated', 'Audit log', 'Multi-tenant'] }, + { name: 'Incident Response', desc: 'Breaches, reporting (72h), mitigation, forensics', features: ['GDPR Art. 33/34', 'BSI channels', 'Forensic hooks'] }, + { name: 'Compliance LLM', desc: 'GPT for text + audio, EU-hosted, with citations', features: ['Self-hosted', 'EU-sovereign', 'Audit-citable'] }, + { name: 'Tender Matching', desc: 'RFQ answers automatically against codebase + policies', features: ['Hours not weeks', 'Win-ready', 'Clause mapping'] }, + { name: 'Academy', desc: 'Online training for management and staff', features: ['Mandatory training', 'Certificates', 'Mgmt duties fulfilled'] }, + { name: 'Compliance Optimizer', desc: 'Max AI use within legal limits, replaces €20-200k legal fees', features: ['ROI ranking', 'Sweet spot', 'Risk balance'] }, + { name: 'Communication', desc: 'Chat (Matrix) + video (Jitsi) + AI support', features: ['Self-hosted', 'EU hosting', 'Audit logs'] }, ] export function PrintProductPage({ products, lang, pageNum, totalPages, versionName }: SlideBase & { products: PitchProduct[] }) { @@ -205,16 +231,22 @@ export function PrintProductPage({ products, lang, pageNum, totalPages, versionN
- {modules.map((m, i) => ( -
-
-
{m.name}
-
{String(i + 1).padStart(2, '0')}
+ {modules.map((m, i) => { + const Icon = MODULE_ICONS[i] + return ( +
+
+
+ +
+
{m.name}
+
{String(i + 1).padStart(2, '0')}
+
+
{m.desc}
+
{m.features.join(' · ')}
-
{m.desc}
-
{m.features.join(' · ')}
-
- ))} + ) + })}
@@ -249,18 +281,8 @@ export function PrintHowItWorksPage({ lang, pageNum, totalPages, versionName }: return ( - {/* horizontal step flow */} -
- {steps.map((s, i) => ( -
- {i < steps.length - 1 && ( -
- )} -
{s.n}
-
{s.t}
-
{s.d}
-
- ))} +
+
diff --git a/pitch-deck/app/pitch-print/[versionId]/page.tsx b/pitch-deck/app/pitch-print/[versionId]/page.tsx index 737749e..1c92a2d 100644 --- a/pitch-deck/app/pitch-print/[versionId]/page.tsx +++ b/pitch-deck/app/pitch-print/[versionId]/page.tsx @@ -65,29 +65,28 @@ export default async function PitchPrintPage({ params, searchParams }: Ctx) { fp_scenarios: (map.fm_scenarios || []) as FpScenarioRef[], } - // Financial variant: fetch FM results + parse assumptions + // Always fetch FM results + assumptions so the standard PDF can render the + // annex-finanzplan slide. The `financial` flag only adds the extra detail + // P&L page and the cap-table page. let fmResults: FMResult[] = [] let fmAssumptions: FMAssumption[] = [] - if (financial) { - const scenarios = (map.fm_scenarios || []) as FpScenarioRef[] - const defaultScenario = scenarios.find(s => s.is_default) ?? scenarios[0] ?? null - - if (defaultScenario?.id) { - const resultsRes = await pool.query( - `SELECT * FROM pitch_fm_results WHERE scenario_id = $1 ORDER BY month`, - [defaultScenario.id], - ) - fmResults = resultsRes.rows as FMResult[] - } - - const rawAssumptions = (map.fm_assumptions || []) as Array> - fmAssumptions = rawAssumptions.map(a => ({ - ...a, - value: typeof a.value === 'string' ? JSON.parse(a.value as string) : a.value, - })) as FMAssumption[] + const scenarios = (map.fm_scenarios || []) as FpScenarioRef[] + const defaultScenario = scenarios.find(s => s.is_default) ?? scenarios[0] ?? null + if (defaultScenario?.id) { + const resultsRes = await pool.query( + `SELECT * FROM pitch_fm_results WHERE scenario_id = $1 ORDER BY month`, + [defaultScenario.id], + ) + fmResults = resultsRes.rows as FMResult[] } + const rawAssumptions = (map.fm_assumptions || []) as Array> + fmAssumptions = rawAssumptions.map(a => ({ + ...a, + value: typeof a.value === 'string' ? JSON.parse(a.value as string) : a.value, + })) as FMAssumption[] + return (