Merge branch 'main' of ssh://gitea.meghsakha.com:22222/Benjamin_Boenisch/breakpilot-core
Build pitch-deck / build-push-deploy (push) Successful in 2m3s
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 38s
CI / test-bqas (push) Successful in 35s

This commit is contained in:
Benjamin Admin
2026-05-20 16:24:00 +02:00
16 changed files with 3765 additions and 1557 deletions
@@ -1,478 +1,468 @@
import { PrintPage, SectionTitle, PrintTable, COLORS } from './PrintLayout'
import { Language, FMResult } from '@/lib/types'
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 }
const STRATEGY_PHASES_DE = [
{ title: 'Phase 1: Foundation', period: 'Aug 2026 Jun 2027', team: '5 MA', arr: '75150k EUR', items: ['Security Engineer + CE-Risikoingenieur als erste Hires', '5 Pilotkunden im Maschinenbau', 'Gründer verkaufen selbst', 'Product-Market Fit beweisen'] },
{ title: 'Phase 2: Traction', period: 'Jul 2027 Jun 2028', team: '10 MA', arr: '0,51,2M EUR', items: ['Channel Manager für Bechtle/CANCOM', 'DevSecOps + KI-Ingenieur', 'Lösungsberater für Partner-Demos', 'Wiederholbarer Vertriebsprozess'] },
{ title: 'Phase 3: Scale', period: 'Jul 2028 Jun 2029', team: '17→25 MA', arr: '24M EUR', items: ['Erster Direktvertrieb neben Channel', 'Compliance-Jurist für Glaubwürdigkeit', 'Security-Analyst / Pentester', 'VP Sales übernimmt vom CEO'] },
{ title: 'Phase 4: Leadership', period: 'Jul 2029 Dez 2030', team: '25→35 MA', arr: '410M EUR', items: ['EU-Expansion (AT, CH, Benelux)', 'Enterprise-Vertrieb', 'Developer Relations (Snyk-Modell)', 'Break-Even oder Series A'] },
]
const STRATEGY_PHASES_EN = [
{ title: 'Phase 1: Foundation', period: 'Aug 2026 Jun 2027', team: '5 emp.', arr: '75150k EUR', items: ['Security Engineer + CE Risk Engineer as first hires', '5 pilot customers in manufacturing', 'Founders sell themselves', 'Prove product-market fit'] },
{ title: 'Phase 2: Traction', period: 'Jul 2027 Jun 2028', team: '10 emp.', arr: '0.51.2M EUR', items: ['Channel Manager for Bechtle/CANCOM', 'DevSecOps + AI engineer', 'Solutions engineer for partner demos', 'Repeatable sales process'] },
{ title: 'Phase 3: Scale', period: 'Jul 2028 Jun 2029', team: '17→25 emp.', arr: '24M EUR', items: ['First direct sales alongside channel', 'Compliance lawyer for credibility', 'Security analyst / pentester', 'VP Sales takes over from CEO'] },
{ title: 'Phase 4: Leadership', period: 'Jul 2029 Dez 2030', team: '25→35 emp.', arr: '410M EUR', items: ['EU expansion (AT, CH, Benelux)', 'Enterprise sales', 'Developer Relations (Snyk model)', 'Break-even or Series A'] },
]
/* The Anhang divider lives in PrintNewSlides.tsx so this file stays under
* the 500-LOC cap. */
/* ===== STRATEGY / GO-TO-MARKET ===== */
export function PrintStrategyPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
const de = lang === 'de'
const phases = de ? STRATEGY_PHASES_DE : STRATEGY_PHASES_EN
const phases = [
{
n: '01', t: de ? 'Pilot, Jul/Aug 2026' : 'Pilot, Jul/Aug 2026',
subtitle: de ? 'Validierung im DACH-Maschinenbau' : 'DACH manufacturing validation',
items: de ? [
'GmbH-Gründung Breakpilot COMPLAI',
'Direktvertrieb an Maschinenbauer 10250 MA',
'White-Glove-Onboarding: persönlich, hands-on',
'2 Referenzkunden aus Region Konstanz/Bodensee',
'Case Studies, Testimonials, Referenz-Calls',
'Ziel: 2 zahlende Kunden, ARR €3050k',
] : [
'GmbH incorporation Breakpilot COMPLAI',
'Direct sales to manufacturers 10250 emp.',
'White-glove onboarding: personal, hands-on',
'2 reference customers from Konstanz/Bodensee region',
'Case studies, testimonials, reference calls',
'Goal: 2 paying customers, ARR €3050k',
],
kpi: de ? '2 Kunden · ARR €40k' : '2 customers · ARR €40k',
tone: COLORS.indigo600,
},
{
n: '02', t: de ? 'Skalierung, 2027' : 'Scale, 2027',
subtitle: de ? 'Channel-Partnerschaften, IHK, Messen' : 'Channel partnerships, IHK, fairs',
items: de ? [
'Channel-Partnerschaften mit IT-Systemhäusern (510)',
'IHK-Kooperationen Bodensee, Stuttgart, München',
'VDMA-Kooperation, Hannover Messe, IT-SA',
'Content-Marketing: Compliance-Webinare 2× / Monat',
'Inbound-Funnel + SEO für AI-Act / NIS2 / CRA',
'Headcount: 57 (Eng + 2 Sales + 1 CSM)',
'Ziel: 5080 Kunden, ARR €1,22M',
] : [
'Channel partnerships with IT integrators (510)',
'IHK partnerships Bodensee, Stuttgart, Munich',
'VDMA cooperation, Hannover Messe, IT-SA',
'Content marketing: compliance webinars 2× / month',
'Inbound funnel + SEO for AI Act / NIS2 / CRA',
'Headcount: 57 (eng + 2 sales + 1 CSM)',
'Goal: 5080 customers, ARR €1.22M',
],
kpi: de ? '5080 Kunden · ARR €1,5M' : '5080 customers · ARR €1.5M',
tone: COLORS.indigo600,
},
{
n: '03', t: de ? 'Expansion, 2028 +' : 'Expansion, 2028 +',
subtitle: de ? 'EU-Skalierung & Enterprise' : 'EU scale & enterprise',
items: de ? [
'Enterprise-Kunden (50500 MA): dedicated AE-Team',
'EU-Expansion: AT (DACH-nativ), CH, Benelux',
'Distributor-Partnerschaften für Frankreich, Italien',
'Branchenausweitung: Automotive, Pharma, Energie',
'Series A vorbereiten (€812M, Q2 2029)',
'Break-Even Q3 / 2029',
'Ziel: 200+ Kunden, ARR €812M',
] : [
'Enterprise customers (50500 emp.): dedicated AE team',
'EU expansion: AT (DACH-native), CH, Benelux',
'Distributor partnerships for France, Italy',
'Industry expansion: Automotive, Pharma, Energy',
'Prepare Series A (€812M, Q2 2029)',
'Break-even Q3 / 2029',
'Goal: 200+ customers, ARR €812M',
],
kpi: de ? '200+ Kunden · Break-Even' : '200+ customers · break-even',
tone: COLORS.emerald600,
},
]
return (
<PrintPage title={de ? 'Strategie' : 'Strategy'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<SectionTitle subtitle={de ? 'Channel-first. Kein Wettbewerber verbindet Code-Security mit Compliance-Automatisierung.' : 'Channel-first. No competitor combines code security with compliance automation.'}>
{de ? 'Anhang · Strategie' : 'Appendix · Strategy'}
</SectionTitle>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '8px', marginBottom: '10px' }}>
{[
{ c: '#ef4444', t: de ? 'Code Security' : 'Code Security', s: 'Snyk, Checkmarx, Veracode', q: de ? '„47 Schwachstellen gefunden. CRA-konform? Nicht unser Problem."' : '"Found 47 vulnerabilities. CRA compliant? Not our problem."' },
{ c: COLORS.indigo, t: 'BreakPilot COMPLAI', s: de ? 'Verbindet beides' : 'Combines both', q: de ? '„Code gescannt, SBOM generiert, CRA gemappt, TOM aktualisiert, CE-Ordner fertig."' : '"Code scanned, SBOM generated, CRA mapped, TOM updated, CE folder ready."' },
{ c: '#06b6d4', t: de ? 'Compliance' : 'Compliance', s: 'DataGuard, Vanta, Drata', q: de ? '„Dokumentation fertig. Code sicher? Brauchen Sie ein anderes Tool."' : '"Documentation done. Code secure? You need a different tool."' },
].map(b => (
<div key={b.t} style={{ border: `1px solid ${COLORS.border}`, borderRadius: '6px', padding: '8px 10px', borderTop: `3px solid ${b.c}`, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<p style={{ fontSize: '11px', fontWeight: 700, color: b.c, margin: '0 0 2px' }}>{b.t}</p>
<p style={{ fontSize: '8px', color: COLORS.light, margin: '0 0 4px' }}>{b.s}</p>
<p style={{ fontSize: '9px', color: COLORS.med, margin: 0, fontStyle: 'italic', lineHeight: 1.4 }}>{b.q}</p>
</div>
))}
</div>
<p style={{ fontSize: '9px', fontWeight: 700, color: COLORS.indigo, textTransform: 'uppercase', letterSpacing: '0.06em', margin: '0 0 6px' }}>{de ? 'Firmenaufbau in 4 Phasen' : 'Company Building in 4 Phases'}</p>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '8px', marginBottom: '10px' }}>
{phases.map(p => (
<div key={p.title} style={{ border: `1px solid ${COLORS.border}`, borderRadius: '6px', padding: '8px 10px', borderTop: `2px solid ${COLORS.indigo}` }}>
<p style={{ fontSize: '10px', fontWeight: 700, color: COLORS.indigo, margin: '0 0 2px' }}>{p.title}</p>
<p style={{ fontSize: '8px', color: COLORS.light, margin: '0 0 2px' }}>{p.period}</p>
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '8px', color: COLORS.med, margin: '0 0 6px' }}>
<span>{p.team}</span><span style={{ fontWeight: 700, color: COLORS.indigo }}>{p.arr}</span>
<Page kicker="16" section={de ? 'ANHANG · STRATEGIE' : 'APPENDIX · STRATEGY'} title={de ? 'Vom Pilot zum skalierbaren Vertrieb in drei Phasen.' : 'From pilot to scalable sales in three phases.'} subtitle={de ? 'Direkter Vertrieb in Phase 1, Channel in Phase 2, Enterprise + EU-Skalierung in Phase 3. Break-Even Q3 / 2029.' : 'Direct sales in phase 1, channel in phase 2, enterprise + EU scale in phase 3. Break-even Q3 / 2029.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<ThreeCol cols={phases.map((p, i) => {
// Split "2 Kunden · ARR €40k" into pieces for a richer outcome block
const kpiParts = p.kpi.split(' · ')
return (
<div key={i} style={{ borderLeft: `2px solid ${p.tone}`, paddingLeft: '5mm', display: 'flex', flexDirection: 'column', height: '100%' }}>
<span style={{ fontSize: '32pt', fontWeight: 800, color: p.tone, lineHeight: 1, fontVariantNumeric: 'tabular-nums', letterSpacing: '-0.02em' }}>{p.n}</span>
<div style={{ fontSize: '13pt', fontWeight: 700, color: COLORS.slate900, marginTop: '3mm', lineHeight: 1.2 }}>{p.t}</div>
<div style={{ fontSize: '8.5pt', color: p.tone, fontWeight: 600, marginBottom: '3mm' }}>{p.subtitle}</div>
<div style={{ flex: 1, minHeight: 0 }}>
<Bullets dense items={p.items} />
</div>
<ul style={{ margin: 0, paddingLeft: '12px', fontSize: '8px', color: COLORS.med, lineHeight: 1.45 }}>
{p.items.map(i => <li key={i}>{i}</li>)}
</ul>
</div>
))}
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '8px' }}>
<div style={{ border: `1px solid ${COLORS.border}`, borderRadius: '6px', padding: '10px', borderTop: `2px solid #3b82f6` }}>
<p style={{ fontSize: '11px', fontWeight: 700, color: '#3b82f6', margin: '0 0 2px' }}>CANCOM Cloud Marketplace</p>
<p style={{ fontSize: '8px', color: COLORS.light, margin: '0 0 4px' }}>{de ? 'TecDAX · ~5.800 MA · 120+ SaaS-Produkte' : 'TecDAX · ~5,800 emp. · 120+ SaaS products'}</p>
<p style={{ fontSize: '9px', color: COLORS.med, margin: 0, lineHeight: 1.5 }}>{de ? 'Formales ISV-Partnerprogramm, Marketplace-Listing in 3-6 Monaten, hunderte CANCOM-Vertriebsmitarbeiter co-sellen.' : 'Formal ISV partner program, marketplace listing in 3-6 months, hundreds of CANCOM reps co-sell.'}</p>
</div>
<div style={{ border: `1px solid ${COLORS.border}`, borderRadius: '6px', padding: '10px', borderTop: `2px solid #10b981` }}>
<p style={{ fontSize: '11px', fontWeight: 700, color: '#10b981', margin: '0 0 2px' }}>Bechtle Systemhäuser</p>
<p style={{ fontSize: '8px', color: COLORS.light, margin: '0 0 4px' }}>{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 style={{ fontSize: '9px', color: COLORS.med, margin: 0, lineHeight: 1.5 }}>{de ? 'Regionaler Einstieg mit lokalem Systemhaus, Champion evangelisiert intern, nationale Listung nach Pilot-Erfolg (12-18 Monate).' : 'Regional entry with local system house, champion evangelizes internally, national listing after pilot success (12-18 months).'}</p>
</div>
</div>
<p style={{ fontSize: '8px', color: COLORS.light, margin: '6px 0 0', fontStyle: '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>
</PrintPage>
)
}
export function PrintFinanzplanPage({ fmResults, lang, pageNum, totalPages, versionName }: SlideBase & { fmResults: FMResult[] }) {
const de = lang === 'de'
const byYear = new Map<number, FMResult[]>()
for (const r of fmResults) {
if (!byYear.has(r.year)) byYear.set(r.year, [])
byYear.get(r.year)!.push(r)
}
const years = Array.from(byYear.entries()).sort(([a], [b]) => a - b).map(([year, rows]) => {
const last = rows[rows.length - 1]
const revenue = rows.reduce((s, r) => s + r.revenue_eur, 0)
return {
year,
revenue,
customers: last?.total_customers ?? 0,
employees: last?.employees_count ?? 0,
arr: last?.arr_eur ?? 0,
mrr: last?.mrr_eur ?? 0,
}
})
const fmt = (n: number) => {
const abs = Math.abs(n)
if (abs >= 1_000_000) return `${(n / 1_000_000).toLocaleString('de-DE', { maximumFractionDigits: 1 })}M`
if (abs >= 1_000) return `${(n / 1_000).toLocaleString('de-DE', { maximumFractionDigits: 0 })}k`
return n.toLocaleString('de-DE')
}
const maxRev = Math.max(1, ...years.map(y => y.revenue))
return (
<PrintPage title={de ? 'Finanzplan' : 'Financial Plan'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<SectionTitle subtitle={de ? '20262030 · Base Case · monatlich modelliert, jährlich aggregiert' : '20262030 · Base Case · monthly model, annual aggregation'}>
{de ? 'Anhang · Finanzplan' : 'Appendix · Financial Plan'}
</SectionTitle>
{years.length === 0 ? (
<div style={{ padding: '14px', border: `1px solid ${COLORS.border}`, borderRadius: '6px', background: '#f5f3ff', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<p style={{ fontSize: '11px', color: COLORS.med, margin: 0, lineHeight: 1.55 }}>
{de
? 'Detaillierter Finanzplan (Umsatz, GuV, Liquidität, Personal, Kunden, Investitionen) ist im Investorenportal und im L-Bank Excel-Template verfügbar. Diese PDF-Version zeigt nur die annualisierten Kennzahlen aus dem Finanzmodell.'
: 'Detailed financial plan (revenue, P&L, liquidity, personnel, customers, capex) is available in the investor portal and L-Bank Excel template. This PDF version shows only annualized KPIs from the financial model.'}
</p>
</div>
) : (
<>
<div style={{ marginBottom: '10px' }}>
<PrintTable
headers={[de ? 'Jahr' : 'Year', de ? 'Umsatz' : 'Revenue', 'ARR (Dec)', 'MRR (Dec)', de ? 'Kunden' : 'Customers', 'FTE']}
rows={years.map(y => [
<strong key="y">{y.year}</strong>,
`${fmt(y.revenue)} EUR`,
`${fmt(y.arr)} EUR`,
`${fmt(y.mrr)} EUR`,
y.customers.toString(),
y.employees.toString(),
])}
colWidths={['10%', '20%', '20%', '20%', '15%', '15%']}
/>
</div>
<div style={{ marginTop: '6px' }}>
<p style={{ fontSize: '9px', fontWeight: 700, color: COLORS.indigo, textTransform: 'uppercase', letterSpacing: '0.06em', margin: '0 0 6px' }}>{de ? 'Umsatzwachstum' : 'Revenue growth'}</p>
{years.map(y => (
<div key={y.year} style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '5px' }}>
<span style={{ fontSize: '9px', color: COLORS.med, minWidth: '36px' }}>{y.year}</span>
<div style={{ flex: 1, height: '12px', background: '#e0e7ff', borderRadius: '3px', overflow: 'hidden', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<div style={{ height: '100%', width: `${(y.revenue / maxRev) * 100}%`, background: COLORS.indigo, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
</div>
<span style={{ fontSize: '9px', fontWeight: 700, color: COLORS.dark, minWidth: '70px', textAlign: 'right', fontFamily: 'ui-monospace, monospace' }}>{fmt(y.revenue)} EUR</span>
{/* Bottom outcome block fills remaining vertical space and reads as a KPI tile */}
<div style={{ marginTop: '4mm', paddingTop: '3mm', borderTop: `1px solid ${COLORS.slate200}` }}>
<div style={{ fontSize: '7pt', color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.12em', fontWeight: 700, marginBottom: '2mm' }}>{de ? 'Outcome' : 'Outcome'}</div>
<div style={{ display: 'grid', gridTemplateColumns: kpiParts.length > 1 ? '1fr 1fr' : '1fr', gap: '3mm' }}>
{kpiParts.map((part, j) => (
<div key={j} style={{ fontSize: '14pt', fontWeight: 800, color: p.tone, lineHeight: 1.05, fontVariantNumeric: 'tabular-nums', letterSpacing: '-0.01em' }}>{part}</div>
))}
</div>
))}
</div>
</div>
</>
)}
<p style={{ fontSize: '8px', color: COLORS.light, marginTop: 'auto', fontStyle: 'italic' }}>
{de ? '* Planzahlen aus dem internen Finanzmodell. SKR04-Kontenrahmen, monatliche Granularität. Detail-Tabs (GuV, Liquidität, Personalkosten, Kundenakquise) im Investor-Portal.' : '* Projections from internal financial model. SKR04 chart of accounts, monthly granularity. Detail tabs (P&L, liquidity, payroll, customer acquisition) in the investor portal.'}
</p>
</PrintPage>
)
})} />
<div style={{ marginTop: '5mm', flexShrink: 0 }}>
<Callout tone="accent" label={de ? 'Vertriebs-Hypothese' : 'Sales hypothesis'}>
{de
? 'Maschinenbauer kaufen über Vertrauen + Referenzen. Phase 1: Beziehungen aufbauen. Phase 2: Multiplikatoren nutzen (Systemhäuser, IHK, VDMA). Phase 3: Inbound + Enterprise-Konten.'
: 'Manufacturers buy via trust + references. Phase 1: build relationships. Phase 2: leverage multipliers (system houses, IHK, VDMA). Phase 3: inbound + enterprise accounts.'}
</Callout>
</div>
</Page>
)
}
const REG_DATA = {
de: [
{ name: 'DSGVO', full: 'EU 2016/679', deadline: 'Seit Mai 2018', fines: 'Bis 20 Mio. EUR / 4% Umsatz', reqs: ['VVT (Art. 30)', 'DSFA (Art. 35)', 'TOMs', 'Betroffenenrechte', 'AV-Verträge', 'DSB ab 20 MA', '72h-Meldepflicht'], help: ['Auto-VVT aus Unternehmensdaten', 'KI-gestützte DSFA', 'TOM-Generator', 'Self-Service Betroffenenportal', 'Audit-Trail'] },
{ name: 'AI Act', full: 'EU 2024/1689', deadline: 'Aug 2025 / 2026 / 2027', fines: 'Bis 35 Mio. EUR / 7% Umsatz', reqs: ['Risikoklassifizierung (Art. 6)', 'Konformitätsbewertung Hochrisiko (Art. 43)', 'Tech. Doku (Art. 11-13)', 'Menschliche Aufsicht (Art. 14)', 'EU-Datenbank-Registrierung (Art. 49)', 'GPAI-Pflichten (Art. 51-56)', 'FRIA (Art. 27)'], help: ['Auto-Risikoklassifizierung', 'Konformitäts-Checklisten', 'Template-basierte Doku', 'Audit-Vorbereitung', 'Monitoring Rechtsänderungen'] },
{ name: 'CRA', full: 'EU 2024/2847', deadline: 'Sep 2026 / Dez 2027', fines: 'Bis 15 Mio. EUR / 2,5% Umsatz', reqs: ['Security by Design', 'Schwachstellen-Mgmt über Lebenszyklus', 'SBOM für jedes Produkt', 'Kostenlose Security-Updates', '24h-Meldepflicht', 'Drittstellen-Bewertung kritisch', 'CE-Kennzeichnung Cyber'], help: ['Auto-SBOM aus Code-Repos', 'Kontinuierliches Vuln-Scanning (Trivy, Grype)', 'Security-Fixes via Cloud-LLM', 'CRA-Doku + Audit-Trail', 'Risikoanalysen Firmware'] },
{ name: 'NIS2', full: 'EU 2022/2555', deadline: 'NIS2UmsuCG 2025/26', fines: 'Bis 10 Mio. EUR / 2% Umsatz', reqs: ['Risikomgmt-Maßnahmen (Art. 21)', '24h Frühwarnung, 72h Bericht (Art. 23)', 'Business Continuity', 'Supply-Chain-Security', 'Geschäftsleiterhaftung', 'BSI-Registrierung', 'Regelmäßige Audits'], help: ['Policy-Generator nach BSI-Grundschutz', 'Incident-Response-Pläne', 'Supply-Chain-Risikoanalyse', 'Audit-Doku', 'NIS2-Readiness-Assessment'] },
],
en: [
{ name: 'GDPR', full: 'EU 2016/679', deadline: 'Since May 2018', fines: 'Up to EUR 20M / 4% revenue', reqs: ['RoPA (Art. 30)', 'DPIA (Art. 35)', 'TOMs', 'Data subject rights', 'DPAs', 'DPO from 20 emp.', '72h breach notification'], help: ['Auto-RoPA from company data', 'AI-powered DPIA', 'TOM generator', 'Self-service data subject portal', 'Audit trail'] },
{ name: 'AI Act', full: 'EU 2024/1689', deadline: 'Aug 2025 / 2026 / 2027', fines: 'Up to EUR 35M / 7% revenue', reqs: ['Risk classification (Art. 6)', 'Conformity assessment high-risk (Art. 43)', 'Technical doc (Art. 11-13)', 'Human oversight (Art. 14)', 'EU database registration (Art. 49)', 'GPAI obligations (Art. 51-56)', 'FRIA (Art. 27)'], help: ['Auto risk classification', 'Conformity checklists', 'Template-based docs', 'Audit prep', 'Regulatory change monitoring'] },
{ name: 'CRA', full: 'EU 2024/2847', deadline: 'Sep 2026 / Dec 2027', fines: 'Up to EUR 15M / 2.5% revenue', reqs: ['Security by design', 'Vuln mgmt across lifecycle', 'SBOM per product', 'Free security updates', '24h reporting', 'Third-party assessment critical', 'CE marking cyber'], help: ['Auto SBOM from code repos', 'Continuous vuln scanning (Trivy, Grype)', 'Security fixes via cloud LLM', 'CRA docs + audit trail', 'Firmware risk assessments'] },
{ name: 'NIS2', full: 'EU 2022/2555', deadline: 'NIS2 Act 2025/26', fines: 'Up to EUR 10M / 2% revenue', reqs: ['Risk mgmt measures (Art. 21)', '24h early warning, 72h report (Art. 23)', 'Business continuity', 'Supply chain security', 'Management liability', 'BSI registration', 'Regular audits'], help: ['Policy generator BSI standards', 'Incident response plans', 'Supply chain risk analysis', 'Audit docs', 'NIS2 readiness assessment'] },
],
}
/* ===== ANNEX REGULATORY ===== */
const PILLARS_DE = [
{ t: 'DSGVO + AI Act', d: 'Datenschutz + KI-Regulierung greifen ineinander. Hochrisiko-KI-Systeme (Art. 6 AI Act) benötigen DSFA + Konformitätsbewertung. BreakPilot generiert beide automatisch.', laws: ['VO 2016/679 (DSGVO)', 'VO 2024/1689 (AI Act)', '§§ 9, 27 BDSG'] },
{ t: 'NIS2 + Cyber Resilience Act', d: 'NIS2 schreibt Sicherheitsmaßnahmen für ~30k DE-Unternehmen vor. CRA addressiert Produkte mit digitalen Elementen. Beide brauchen kontinuierliches Vulnerability-Management.', laws: ['RL 2022/2555 (NIS2)', 'VO 2024/2847 (CRA)', '§§ 28 ff. BSIG'] },
{ t: 'Maschinen-VO + ProdSG', d: 'Neue EU-Maschinenverordnung (ab 2027 verpflichtend) fordert Software-Risikobeurteilung schon auf Code-Ebene. Klassische CE-Verfahren reichen nicht mehr.', laws: ['VO 2023/1230 (MaschVO)', 'ProdSG 2021', 'EN ISO 12100'] },
{ t: 'DORA + Branchenrecht', d: 'Finanzsektor: DORA. Gesundheit: MDR/IVDR. Öffentlicher Sektor: OZG. KRITIS: BSI-KritisV. BreakPilot deckt 380+ Regularien, branchenagnostisch.', laws: ['VO 2022/2554 (DORA)', 'VO 2017/745 (MDR)', 'BSI-KritisV'] },
]
const PILLARS_EN = [
{ t: 'GDPR + AI Act', d: 'Data protection + AI regulation interlock. High-risk AI systems (AI Act Art. 6) require DPIA + conformity assessment. BreakPilot generates both automatically.', laws: ['Reg. 2016/679 (GDPR)', 'Reg. 2024/1689 (AI Act)', 'BDSG §§ 9, 27'] },
{ t: 'NIS2 + Cyber Resilience Act', d: 'NIS2 mandates security measures for ~30k DE companies. CRA addresses products with digital elements. Both require continuous vulnerability management.', laws: ['Dir. 2022/2555 (NIS2)', 'Reg. 2024/2847 (CRA)', 'BSIG §§ 28 ff.'] },
{ t: 'Machinery Reg. + ProdSG', d: 'New EU Machinery Regulation (mandatory from 2027) requires software risk assessment at code level. Traditional CE procedures are no longer sufficient.', laws: ['Reg. 2023/1230 (Mach. Reg.)', 'ProdSG 2021', 'EN ISO 12100'] },
{ t: 'DORA + Industry Law', d: 'Finance: DORA. Health: MDR/IVDR. Public sector: OZG. KRITIS: BSI-KritisV. BreakPilot covers 380+ regulations, industry-agnostic.', laws: ['Reg. 2022/2554 (DORA)', 'Reg. 2017/745 (MDR)', 'BSI-KritisV'] },
]
export function PrintRegulatoryPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
const de = lang === 'de'
const regs = de ? REG_DATA.de : REG_DATA.en
const pillars = de ? PILLARS_DE : PILLARS_EN
return (
<PrintPage title={de ? 'Regulatorik' : 'Regulatory'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<SectionTitle subtitle={de ? 'Die vier Säulen der EU-Compliance für Maschinenbauer' : 'The four pillars of EU compliance for manufacturers'}>
{de ? 'Anhang · Regulatorische Details' : 'Appendix · Regulatory Details'}
</SectionTitle>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '8px', flex: 1 }}>
{regs.map(r => (
<div key={r.name} style={{ border: `1px solid ${COLORS.border}`, borderRadius: '6px', padding: '8px 10px', borderTop: `2px solid ${COLORS.indigo}`, display: 'flex', flexDirection: 'column' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: '2px' }}>
<p style={{ fontSize: '12px', fontWeight: 800, color: COLORS.dark, margin: 0 }}>{r.name}</p>
<p style={{ fontSize: '8px', color: COLORS.light, margin: 0, fontFamily: 'ui-monospace, monospace' }}>{r.full}</p>
<Page kicker="19" section={de ? 'ANHANG · REGULATORISCHE DETAILS' : 'APPENDIX · REGULATORY DETAILS'} title={de ? 'Vier Säulen der EU-Compliance für Maschinenbauer.' : 'Four pillars of EU compliance for manufacturers.'} subtitle={de ? 'Jede Säule deckt 46 verbindliche Regelwerke ab. BreakPilot mappt diese auf 25.000+ atomare Controls.' : 'Each pillar covers 46 binding regulations. BreakPilot maps these to 25,000+ atomic controls.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '5mm', flex: 1, minHeight: 0 }}>
{pillars.map((p, i) => (
<div key={i} style={{ border: `1px solid ${COLORS.slate200}`, borderTop: `2px solid ${COLORS.indigo600}`, padding: '4mm 5mm', display: 'flex', flexDirection: 'column' }}>
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: '2mm' }}>
<span style={{ fontSize: '7.5pt', fontWeight: 700, color: COLORS.indigo600, textTransform: 'uppercase', letterSpacing: '0.12em' }}>{de ? `Säule ${String(i + 1).padStart(2, '0')}` : `Pillar ${String(i + 1).padStart(2, '0')}`}</span>
<span style={{ fontSize: '6.5pt', color: COLORS.slate400 }}>{de ? '4-6 Regelwerke' : '4-6 regulations'}</span>
</div>
<div style={{ display: 'flex', gap: '10px', fontSize: '8px', color: COLORS.med, margin: '0 0 6px' }}>
<span>📅 {r.deadline}</span><span style={{ color: '#dc2626' }}> {r.fines}</span>
</div>
<div style={{ flex: 1, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8px' }}>
<div>
<p style={{ fontSize: '8px', fontWeight: 700, color: COLORS.indigo, textTransform: 'uppercase', letterSpacing: '0.04em', margin: '0 0 3px' }}>{de ? 'Anforderungen' : 'Requirements'}</p>
<ul style={{ margin: 0, paddingLeft: '12px', fontSize: '8px', color: COLORS.med, lineHeight: 1.4 }}>
{r.reqs.slice(0, 5).map(x => <li key={x}>{x}</li>)}
</ul>
</div>
<div>
<p style={{ fontSize: '8px', fontWeight: 700, color: '#16a34a', textTransform: 'uppercase', letterSpacing: '0.04em', margin: '0 0 3px' }}>{de ? 'Wie wir helfen' : 'How we help'}</p>
<ul style={{ margin: 0, paddingLeft: '12px', fontSize: '8px', color: COLORS.med, lineHeight: 1.4 }}>
{r.help.slice(0, 5).map(x => <li key={x}>{x}</li>)}
</ul>
</div>
<div style={{ fontSize: '13pt', fontWeight: 700, color: COLORS.slate900, marginBottom: '3mm', lineHeight: 1.2 }}>{p.t}</div>
<div style={{ fontSize: '8.5pt', color: COLORS.slate700, lineHeight: 1.5, flex: 1, marginBottom: '3mm' }}>{p.d}</div>
<div style={{ borderTop: `1px solid ${COLORS.slate100}`, paddingTop: '2mm', fontSize: '7pt', color: COLORS.slate500, fontFamily: 'monospace' }}>
{p.laws.join(' · ')}
</div>
</div>
))}
</div>
</PrintPage>
<div style={{ marginTop: '4mm', flexShrink: 0 }}>
<Callout tone="caution" label={de ? 'Bußgeld-Risiko' : 'Penalty risk'}>
{de
? 'DSGVO: bis zu 4% Jahresumsatz · AI Act: bis zu 7% bei verbotener KI · NIS2: bis zu 2% · CRA: bis zu 2% · MDR/IVDR: produktspezifisch, Rückruf möglich. Kontinuierliche Compliance senkt das Risiko gegen Null.'
: 'GDPR: up to 4% annual revenue · AI Act: up to 7% for prohibited AI · NIS2: up to 2% · CRA: up to 2% · MDR/IVDR: product-specific, recall possible. Continuous compliance reduces risk to near zero.'}
</Callout>
</div>
</Page>
)
}
const ARCH_NODES_DE = [
{ id: 'certifai', title: 'CERTifAI', sub: 'GenAI Mandantenportal', tech: 'Rust · Dioxus · MongoDB · Keycloak · SearXNG · LangGraph', services: ['LiteLLM Dashboard', 'LibreChat + SSO', 'LangGraph Agents', 'MCP Hub'] },
{ id: 'complai', title: 'COMPLAI', sub: 'Compliance & Audit', tech: 'Next.js 15 · FastAPI · Go/Gin · PostgreSQL · Qdrant', services: ['DSGVO / AI Act / NIS2 (70k+ Controls)', 'RAG Pipeline (75+ Quellen)', 'Control Pipeline (LLM)', 'MCP Client'] },
{ id: 'scanner', title: 'Compliance Scanner', sub: 'Code-Sicherheit', tech: 'Rust · Axum · MongoDB · Semgrep · Gitleaks · Syft', services: ['SAST / SBOM / CVE Pipeline', 'KI-Triage (LLM-Filter)', 'KI-Pentest (autonom)', 'MCP Server'] },
{ id: 'litellm', title: 'LiteLLM Proxy', sub: 'KI-Gateway & Guardrails', tech: 'OpenAI-API · Bearer Auth · Rate Limiting · PII-Filter', services: ['Token-Budget pro Mandant', 'PII Guardrails', 'SearXNG Web-Suche (anonym)', 'Namespace-Isolierung', 'Failover-Routing'] },
{ id: 'llm', title: 'LLM Inferenz', sub: 'Lokale Sprachmodelle', tech: 'Qwen3-32B · Qwen3-Coder-30B · DeepSeek-R1-8B · Ollama', services: ['Vollständig lokal', 'Air-Gap-fähig', 'GPU-optimiert'] },
{ id: 'embeddings', title: 'Embeddings', sub: 'Semantische Suche', tech: 'bge-m3 · Qdrant · Sentence-Transformers', services: ['RAG (75+ Quellen)', 'Multi-linguale Embeddings', '100% lokal'] },
{ id: 'tools', title: 'KI-Tools', sub: 'Web-Suche & MCP', tech: 'SearXNG · MCP Protocol · Semgrep API · Gitleaks API', services: ['Anonymisierte EU-Websuche', 'MCP Tools (Audit/Code)', 'Kein US-Anbieter'] },
]
const ARCH_NODES_EN = [
{ id: 'certifai', title: 'CERTifAI', sub: 'GenAI Tenant Portal', tech: 'Rust · Dioxus · MongoDB · Keycloak · SearXNG · LangGraph', services: ['LiteLLM Dashboard', 'LibreChat + SSO', 'LangGraph Agents', 'MCP Hub'] },
{ id: 'complai', title: 'COMPLAI', sub: 'Compliance & Audit', tech: 'Next.js 15 · FastAPI · Go/Gin · PostgreSQL · Qdrant', services: ['GDPR / AI Act / NIS2 (70k+ controls)', 'RAG Pipeline (75+ sources)', 'Control Pipeline (LLM)', 'MCP Client'] },
{ id: 'scanner', title: 'Compliance Scanner', sub: 'Code Security', tech: 'Rust · Axum · MongoDB · Semgrep · Gitleaks · Syft', services: ['SAST / SBOM / CVE pipeline', 'AI triage (LLM filter)', 'AI pentest (autonomous)', 'MCP Server'] },
{ id: 'litellm', title: 'LiteLLM Proxy', sub: 'AI Gateway & Guardrails', tech: 'OpenAI API · Bearer Auth · Rate Limiting · PII filter', services: ['Token budget per tenant', 'PII guardrails', 'SearXNG web search (anon)', 'Namespace isolation', 'Failover routing'] },
{ id: 'llm', title: 'LLM Inference', sub: 'Local Language Models', tech: 'Qwen3-32B · Qwen3-Coder-30B · DeepSeek-R1-8B · Ollama', services: ['Fully local', 'Air-gap capable', 'GPU optimized'] },
{ id: 'embeddings', title: 'Embeddings', sub: 'Semantic Search', tech: 'bge-m3 · Qdrant · Sentence-Transformers', services: ['RAG (75+ sources)', 'Multi-lingual embeddings', '100% local'] },
{ id: 'tools', title: 'AI Tools', sub: 'Web Search & MCP', tech: 'SearXNG · MCP Protocol · Semgrep API · Gitleaks API', services: ['Anonymized EU web search', 'MCP tools (audit/code)', 'No US providers'] },
]
/* ===== ANNEX ARCHITECTURE ===== */
export function PrintArchitecturePage({ lang, pageNum, totalPages, versionName }: SlideBase) {
const de = lang === 'de'
const nodes = de ? ARCH_NODES_DE : ARCH_NODES_EN
return (
<PrintPage title={de ? 'Architektur' : 'Architecture'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<SectionTitle subtitle={de ? 'BreakPilot · CERTifAI · Compliance Scanner — verbunden über LiteLLM Proxy, alle EU-gehostet' : 'BreakPilot · CERTifAI · Compliance Scanner — connected via LiteLLM proxy, all EU-hosted'}>
{de ? 'Anhang · Systemarchitektur' : 'Appendix · System Architecture'}
</SectionTitle>
<div style={{ flex: 1, display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '6px' }}>
{nodes.map(n => (
<div key={n.id} style={{ border: `1px solid ${COLORS.border}`, borderRadius: '6px', padding: '6px 9px', borderLeft: `3px solid ${COLORS.indigo}` }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: '3px' }}>
<p style={{ fontSize: '11px', fontWeight: 700, color: COLORS.dark, margin: 0 }}>{n.title}</p>
<p style={{ fontSize: '8px', color: COLORS.light, margin: 0 }}>{n.sub}</p>
</div>
<p style={{ fontSize: '8px', color: COLORS.indigo, fontFamily: 'ui-monospace, monospace', margin: '0 0 4px', lineHeight: 1.35 }}>{n.tech}</p>
<ul style={{ margin: 0, paddingLeft: '12px', fontSize: '8px', color: COLORS.med, lineHeight: 1.4 }}>
{n.services.map(s => <li key={s}>{s}</li>)}
</ul>
</div>
))}
</div>
</PrintPage>
<Page kicker="20" section={de ? 'ANHANG · SYSTEMARCHITEKTUR' : 'APPENDIX · SYSTEM ARCHITECTURE'} title={de ? 'Drei Produkt-Domänen. Ein LiteLLM-Gateway. Lokale Inferenz.' : 'Three product domains. One LiteLLM gateway. Local inference.'} subtitle={de ? 'BreakPilot · CERTifAI · Compliance Scanner, über LiteLLM-Proxy mit lokalen Inferenz-Knoten verbunden. 100% EU, kein US-Anbieter.' : 'BreakPilot · CERTifAI · Compliance Scanner, connected via LiteLLM proxy to local inference nodes. 100% EU, no US providers.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<ArchitectureDiagram
lang={lang}
product={[
{ kicker: 'GENAI', title: 'CERTifAI', subtitle: de ? 'GenAI Mandantenportal' : 'GenAI Tenant Portal', tech: 'Rust · Dioxus · MongoDB · Keycloak · SearXNG · LangGraph', services: ['LiteLLM Dashboard', 'LibreChat + SSO', 'LangGraph Agents', 'MCP Hub'] },
{ kicker: 'COMPLIANCE', title: 'COMPLAI', subtitle: 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'] },
{ kicker: 'SECURITY', title: 'Compliance Scanner', subtitle: 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'] },
]}
proxy={{
title: 'LiteLLM Proxy',
subtitle: de ? 'KI-Gateway · Bearer-Auth · Rate-Limiting · PII-Filter · Spend-Tracking' : 'AI gateway · bearer auth · rate limiting · PII filter · spend tracking',
features: [
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={[
{ title: de ? 'LLM Inferenz' : 'LLM Inference', subtitle: de ? 'Lokale Sprachmodelle' : 'Local language models', 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.' },
{ title: 'Embeddings', subtitle: de ? 'Semantische Suche' : 'Semantic search', 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.' },
{ title: de ? 'KI-Tools' : 'AI Tools', subtitle: de ? 'Web-Suche & MCP' : 'Web search & MCP', 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.' },
]}
/>
</Page>
)
}
/* ===== ANNEX ENGINEERING ===== */
export function PrintEngineeringPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
const de = lang === 'de'
const stats = [
{ v: '500K+', l: de ? 'Zeilen Code' : 'Lines of code', s: 'Go · Python · TypeScript' },
{ v: '385', l: de ? 'Dokumente im RAG' : 'Docs in RAG', s: 'EU · DACH · Frameworks · Urteile' },
{ v: '25K+', l: de ? 'Compliance Controls' : 'Compliance Controls', s: '6 Pipeline-Versionen' },
]
const langs = [
{ lang: 'TypeScript / TSX', pct: 49, loc: '235K', color: '#3b82f6' },
{ lang: 'Python', pct: 28, loc: '133K', color: '#eab308' },
{ lang: 'Go', pct: 23, loc: '113K', color: '#06b6d4' },
]
const devops = [
{ l: 'Gitea + Actions', d: de ? 'Self-hosted Git + CI/CD · Lint → Tests → Image-Build' : 'Self-hosted Git + CI/CD · Lint → Tests → Image build' },
{ l: 'orca', d: de ? 'Single-Binary Orchestrator (Rust) · Webhook-Deploy · Auto-TLS' : 'Single-binary orchestrator (Rust) · Webhook deploys · Auto-TLS' },
{ l: 'Private Registry', d: 'registry.meghsakha.com · Signed images · Per-commit tags' },
{ l: 'DevSecOps', d: 'Semgrep · Trivy · Gitleaks · CycloneDX SBOM' },
{ l: 'Infisical', d: de ? 'Secrets Mgmt · Rotation · RBAC · End-to-End-verschlüsselt' : 'Secrets mgmt · Rotation · RBAC · End-to-end encrypted' },
{ l: de ? 'EU-Cloud Infrastruktur' : 'EU Cloud Infrastructure', d: 'Hetzner · SysEleven (BSI) · PostgreSQL · Qdrant' },
]
return (
<PrintPage title={de ? 'Engineering' : 'Engineering'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<SectionTitle subtitle={de ? '500K+ Zeilen Code · 45 Container · 100% Self-Hosted in EU' : '500K+ lines of code · 45 containers · 100% self-hosted in EU'}>
{de ? 'Anhang · Engineering Deep Dive' : 'Appendix · Engineering Deep Dive'}
</SectionTitle>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '8px', marginBottom: '10px' }}>
{stats.map(s => (
<div key={s.l} style={{ background: COLORS.indigoLight, padding: '10px', borderRadius: '6px', textAlign: 'center', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<p style={{ fontSize: '22px', fontWeight: 800, color: COLORS.indigo, margin: 0, lineHeight: 1 }}>{s.v}</p>
<p style={{ fontSize: '10px', fontWeight: 700, color: COLORS.dark, margin: '4px 0 2px' }}>{s.l}</p>
<p style={{ fontSize: '8px', color: COLORS.light, margin: 0 }}>{s.s}</p>
<Page kicker="21" section={de ? 'ANHANG · ENGINEERING' : 'APPENDIX · ENGINEERING'} title={de ? '500K+ Zeilen Code. 45 Container. 100% Self-Hosted.' : '500K+ lines of code. 45 containers. 100% self-hosted.'} subtitle={de ? 'Polyglott (Go/Python/TypeScript/Rust), Microservice-Architektur, vollständig containerisiert. Built for sovereignty.' : 'Polyglot (Go/Python/TypeScript/Rust), microservice architecture, fully containerized. Built for sovereignty.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '4mm', padding: '4mm 0', borderTop: `1px solid ${COLORS.slate200}`, borderBottom: `1px solid ${COLORS.slate200}`, marginBottom: '5mm' }}>
{[
{ n: '500K+', l: 'Lines of Code' },
{ n: '45', l: de ? 'Container' : 'Containers' },
{ n: '4', l: de ? 'Sprachen' : 'Languages' },
{ n: '100%', l: 'Self-Hosted' },
].map((k, i) => (
<div key={i}>
<div style={{ fontSize: '24pt', fontWeight: 800, color: COLORS.indigo600, lineHeight: 1, fontVariantNumeric: 'tabular-nums' }}>{k.n}</div>
<div style={{ fontSize: '7.5pt', color: COLORS.slate500, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.08em', marginTop: '1mm' }}>{k.l}</div>
</div>
))}
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1.4fr', gap: '12px', flex: 1 }}>
<div style={{ flex: 1, minHeight: 0, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8mm' }}>
<div>
<p style={{ fontSize: '9px', fontWeight: 700, color: COLORS.indigo, textTransform: 'uppercase', letterSpacing: '0.06em', margin: '0 0 6px' }}>{de ? 'Sprachen-Mix' : 'Language Mix'}</p>
<div style={{ display: 'flex', height: '10px', borderRadius: '4px', overflow: 'hidden', marginBottom: '8px' }}>
{langs.map(l => <div key={l.lang} style={{ width: `${l.pct}%`, background: l.color, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />)}
</div>
{langs.map(l => (
<div key={l.lang} style={{ display: 'flex', justifyContent: 'space-between', fontSize: '9px', color: COLORS.med, padding: '3px 0' }}>
<span style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
<span style={{ width: '7px', height: '7px', borderRadius: '99px', background: l.color, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
{l.lang}
</span>
<span><span style={{ fontFamily: 'ui-monospace, monospace', color: COLORS.light, marginRight: '8px' }}>{l.loc}</span><strong style={{ color: COLORS.dark }}>{l.pct}%</strong></span>
</div>
))}
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '2mm' }}>{de ? 'Tech-Stack pro Schicht' : 'Tech stack per layer'}</div>
<DataTable
cols={[
{ header: de ? 'Schicht' : 'Layer', width: '28%' },
{ header: 'Stack' },
]}
rows={[
[de ? 'Frontend' : 'Frontend', 'Next.js 15 · React 19 · Tailwind · Framer Motion · Dioxus (Rust)'],
[de ? 'Backend (HTTP)' : 'Backend (HTTP)', 'Go/Gin · Python/FastAPI · Rust/Axum'],
[de ? 'Storage' : 'Storage', 'PostgreSQL 16 · MongoDB · Qdrant (vector) · Valkey (cache)'],
[de ? 'KI / RAG' : 'AI / RAG', 'LiteLLM · Qwen3 · DeepSeek · Sentence-Transformers · LangGraph'],
[de ? 'Code-Scanning' : 'Code scanning', 'Semgrep · Gitleaks · Syft · Trivy · CycloneDX'],
[de ? 'Auth & SSO' : 'Auth & SSO', 'Keycloak · OIDC · OPA (policies)'],
[de ? 'Kommunikation' : 'Communication', 'Matrix (chat) · Jitsi (video) · Mailpit'],
[de ? 'DevOps' : 'DevOps', 'Gitea · Woodpecker CI · Vault · Orca · Docker Compose'],
]}
dense
highlightFirstCol
/>
</div>
<div>
<p style={{ fontSize: '9px', fontWeight: 700, color: COLORS.indigo, textTransform: 'uppercase', letterSpacing: '0.06em', margin: '0 0 6px' }}>{de ? 'DevOps & Toolchain' : 'DevOps & Toolchain'}</p>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px' }}>
{devops.map(t => (
<div key={t.l} style={{ border: `1px solid ${COLORS.border}`, borderRadius: '4px', padding: '6px 8px' }}>
<p style={{ fontSize: '10px', fontWeight: 700, color: COLORS.dark, margin: '0 0 2px' }}>{t.l}</p>
<p style={{ fontSize: '8px', color: COLORS.med, margin: 0, lineHeight: 1.35 }}>{t.d}</p>
</div>
))}
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '2mm' }}>{de ? 'Engineering-Prinzipien' : 'Engineering principles'}</div>
<Bullets dense items={de ? [
'AGENTS.md · CLAUDE.md · architektur-getrieben',
'500-LOC Cap pro Datei, keine Mega-Files',
'Microservices: jeder Service in eigenem Container',
'Service-Layer + Repository-Pattern (Go), Routes/Services/Repos (Python)',
'Pre-Push Checks PFLICHT: tsc + lint + build (TS), ruff + mypy + pytest (Py), gofmt + vet + lint + test (Go)',
'Open Source nur mit MIT/Apache-2.0/BSD/ISC/MPL-2.0/LGPL, keine GPL/AGPL',
'Self-Hosted CI (Gitea Actions, Woodpecker), keine US-Cloud-CI',
'Secret-Management über HashiCorp Vault',
'Tests sind Pflicht bei jeder Code-Änderung',
'BSI-Cloud DE, EU-Hosting, kein US-SaaS in Source Code',
] : [
'AGENTS.md · CLAUDE.md · architecture-driven',
'500-LOC cap per file, no mega-files',
'Microservices: each service in its own container',
'Service layer + repository pattern (Go), routes/services/repos (Python)',
'Pre-push checks MANDATORY: tsc + lint + build (TS), ruff + mypy + pytest (Py), gofmt + vet + lint + test (Go)',
'Open source only with MIT/Apache-2.0/BSD/ISC/MPL-2.0/LGPL, no GPL/AGPL',
'Self-hosted CI (Gitea Actions, Woodpecker), no US cloud CI',
'Secret management via HashiCorp Vault',
'Tests required on every code change',
'BSI cloud DE, EU hosting, no US SaaS in source code',
]} />
<div style={{ marginTop: '4mm' }}>
<Callout tone="accent" label={de ? 'Engineering-Velocity' : 'Engineering velocity'}>
{de
? 'Seit Jan 2026: 500.000+ LoC in 4 Monaten. Mit Series-A-Funding: 2× Senior Engineers, Velocity verdoppelt sich. Architektur skaliert auf 10+ Engineers ohne Refactor.'
: 'Since Jan 2026: 500,000+ LoC in 4 months. With Series A funding: 2× senior engineers, velocity doubles. Architecture scales to 10+ engineers without refactor.'}
</Callout>
</div>
</div>
</div>
<p style={{ fontSize: '8px', color: COLORS.light, marginTop: '8px', textAlign: 'center' }}>
{de ? '100% EU-Cloud · Hetzner + SysEleven (BSI) · Keine US-Anbieter · Volle Datenkontrolle' : '100% EU Cloud · Hetzner + SysEleven (BSI) · No US providers · Full data control'}
</p>
</PrintPage>
</Page>
)
}
const PIPELINE_STEPS = {
de: [
{ t: '1. Dokument-Ingestion', d: '380+ Rechtsquellen (EU/DACH). Strukturelles Chunking an Artikel-Grenzen. Lizenz-Klassifikation. Geschützte Quellen werden reformuliert.' },
{ t: '2. Control-Extraktion', d: 'LLM extrahiert Pflichten aus jedem Textabschnitt. 6 Pipeline-Versionen. ~97k Pflichten identifiziert. Atomic Control Composition.' },
{ t: '3. Deduplizierung', d: '97k Controls → 70k+ nach Dedup. Embedding-basierte Similarity. Cross-Regulation-Harmonisierung. Master Controls als Single Source of Truth.' },
{ t: '4. Hybrid Search & Beratung', d: 'Vektor + Keyword über alle Quellen gleichzeitig. Cross-Encoder Re-Ranking. Antworten mit Quellen-Attribution (Artikel + Absatz).' },
],
en: [
{ t: '1. Document Ingestion', d: '380+ legal sources (EU/DACH). Structural chunking at article boundaries. License classification. Protected standards are reformulated.' },
{ t: '2. Control Extraction', d: 'LLM extracts obligations from each section. 6 pipeline versions. ~97k duties identified. Atomic control composition.' },
{ t: '3. Deduplication', d: '97k controls → 70k+ after dedup. Embedding-based similarity. Cross-regulation harmonization. Master controls as single source of truth.' },
{ t: '4. Hybrid Search & Advisory', d: 'Vector + keyword across all sources simultaneously. Cross-encoder re-ranking. Answers with source attribution (article + paragraph).' },
],
}
/* ===== ANNEX AI PIPELINE ===== */
export function PrintAIPipelinePage({ lang, pageNum, totalPages, versionName }: SlideBase) {
const de = lang === 'de'
const stats = [
{ v: '380+', l: de ? 'Rechtsquellen' : 'Legal sources' },
{ v: '70k+', l: de ? 'Unique Controls' : 'Unique controls' },
{ v: '97k+', l: de ? 'Extrahierte Pflichten' : 'Extracted obligations' },
{ v: '6', l: de ? 'Pipeline-Versionen' : 'Pipeline versions' },
]
const agents = de
? ['UCCA: Policy Engine (45 Regeln) + Eskalation E0E3', 'Pflichten-Engine: Multi-Regulation (NIS2, DSGVO, AI Act, CRA, ...)', 'Compliance-Berater: Legal RAG + LLM Chatbot, 75+ Quellen', 'Dokument-Generator: AGB, DSE, AVV, DSFA, FRIA, BV …', 'DSFA-Agent: Art. 35 DSGVO, 16 Bundesländer-Leitlinien', 'Control-Pipeline: Auto-Extraktion aus neuen Rechtsquellen']
: ['UCCA: Policy engine (45 rules) + escalation E0E3', 'Obligations Engine: Multi-regulation (NIS2, GDPR, AI Act, CRA, ...)', 'Compliance Advisor: Legal RAG + LLM chatbot, 75+ sources', 'Document Generator: T&C, Privacy, DPA, DPIA, FRIA, Works Agreement …', 'DPIA Agent: Art. 35 GDPR, 16 federal state guidelines', 'Control Pipeline: Auto-extraction from new legal sources']
const steps = de ? PIPELINE_STEPS.de : PIPELINE_STEPS.en
return (
<PrintPage title={de ? 'KI-Pipeline' : 'AI Pipeline'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<SectionTitle subtitle={de ? 'RAG · Multi-Agent-System · Document Intelligence · 100% EU-Hosting' : 'RAG · Multi-agent system · Document intelligence · 100% EU hosting'}>
{de ? 'Anhang · KI-Pipeline Deep Dive' : 'Appendix · AI Pipeline Deep Dive'}
</SectionTitle>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '8px', marginBottom: '10px' }}>
{stats.map(s => (
<div key={s.l} style={{ background: COLORS.indigoLight, padding: '8px', borderRadius: '6px', textAlign: 'center', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<p style={{ fontSize: '18px', fontWeight: 800, color: COLORS.indigo, margin: 0 }}>{s.v}</p>
<p style={{ fontSize: '9px', color: COLORS.med, margin: '2px 0 0', textTransform: 'uppercase', letterSpacing: '0.04em' }}>{s.l}</p>
<Page kicker="22" section={de ? 'ANHANG · KI-PIPELINE' : 'APPENDIX · AI PIPELINE'} title={de ? 'RAG · Multi-Agent-System · Document Intelligence · Quality Assurance.' : 'RAG · Multi-Agent System · Document Intelligence · Quality Assurance.'} subtitle={de ? 'Vier Stufen, von Rohdokument zu auditierbarem Control. Vollständig EU-lokal, kein US-LLM, kein Vendor-Lock-in.' : 'Four stages, from raw document to auditable control. Fully EU-local, no US LLM, no vendor lock-in.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<div style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', gap: '4mm' }}>
{/* Pipeline flow */}
<div>
<div style={{ fontSize: '7.5pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.12em', marginBottom: '2mm' }}>{de ? 'Pipeline-Fluss' : 'Pipeline flow'}</div>
<PipelineFlow
stages={[
{ n: '01', t: 'Ingestion', d: de ? 'Rohdokumente (PDF, HTML, XML, Word) → Pass 0a OCR → Pass 0b Strukturierung. Markdown + Metadaten.' : 'Raw docs (PDF, HTML, XML, Word) → Pass 0a OCR → Pass 0b structuring. Markdown + metadata.', kpi: '385 docs' },
{ n: '02', t: 'Chunking + Embed', d: de ? 'Semantisches Chunking, Sentence-Transformers (bge-m3), Qdrant Vector DB, Multi-Index.' : 'Semantic chunking, sentence-transformers (bge-m3), Qdrant vector DB, multi-index.', kpi: '~280k chunks' },
{ n: '03', t: 'Control Extraction', d: de ? 'BatchDedup → LLM-Extraktor (Qwen3-32B) → Strukturierte Controls mit Quellenangabe + Confidence.' : 'BatchDedup → LLM extractor (Qwen3-32B) → structured controls with source + confidence.', kpi: '25k+ controls' },
{ n: '04', t: 'Quality Assurance', d: de ? 'BQAS (Batch Quality Assessment System): Cross-Validation, Inkonsistenz-Detection, Audit-Sampling.' : 'BQAS (Batch Quality Assessment System): cross-validation, inconsistency detection, audit sampling.', kpi: '>99% precision' },
]}
/>
</div>
{/* Agent system */}
<div style={{ flex: 1, minHeight: 0, display: 'grid', gridTemplateColumns: '1.4fr 1fr', gap: '6mm' }}>
<div>
<div style={{ fontSize: '7.5pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.12em', marginBottom: '2mm' }}>{de ? 'Multi-Agent-System (LangGraph)' : 'Multi-Agent System (LangGraph)'}</div>
<DataTable
cols={[
{ header: 'Agent', width: '28%' },
{ header: de ? 'Verantwortung' : 'Responsibility' },
{ header: 'Model', width: '24%' },
]}
rows={[
['Researcher', de ? 'Recherchiert in RAG + SearXNG, sammelt Quellen' : 'Searches RAG + SearXNG, gathers sources', 'Qwen3-32B'],
['Drafter', de ? 'Erstellt VVT, TOMs, DSFA, Policies' : 'Drafts RoPA, TOMs, DPIA, policies', 'Qwen3-32B'],
['Reviewer', de ? 'Prüft auf Vollständigkeit + Compliance' : 'Checks completeness + compliance', 'DeepSeek-R1-8B'],
['Coder', de ? 'Auto-Fix für Code-Findings (SAST/DAST)' : 'Auto-fix for code findings (SAST/DAST)', 'Qwen3-Coder-30B'],
['Pentester', de ? 'Autonome Angriffsketten + Exploitability' : 'Autonomous attack chains + exploitability', 'Qwen3-32B'],
['Supervisor', de ? 'Orchestriert, prüft Konsistenz, eskaliert' : 'Orchestrates, checks consistency, escalates', 'DeepSeek-R1-8B'],
]}
dense
highlightFirstCol
/>
</div>
))}
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1.3fr 1fr', gap: '10px', flex: 1 }}>
<div>
<p style={{ fontSize: '9px', fontWeight: 700, color: COLORS.indigo, textTransform: 'uppercase', letterSpacing: '0.06em', margin: '0 0 6px' }}>{de ? 'RAG-Pipeline (4 Stufen)' : 'RAG Pipeline (4 stages)'}</p>
{steps.map(s => (
<div key={s.t} style={{ borderLeft: `2px solid ${COLORS.indigo}`, paddingLeft: '8px', marginBottom: '6px' }}>
<p style={{ fontSize: '10px', fontWeight: 700, color: COLORS.dark, margin: '0 0 2px' }}>{s.t}</p>
<p style={{ fontSize: '9px', color: COLORS.med, margin: 0, lineHeight: 1.45 }}>{s.d}</p>
<div>
<div style={{ fontSize: '7.5pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.12em', marginBottom: '2mm' }}>{de ? 'Qualitäts-Kennzahlen' : 'Quality metrics'}</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '0' }}>
{[
{ l: de ? 'Precision (Controls)' : 'Precision (controls)', v: '>99%' },
{ l: 'Recall (RAG)', v: '~94%' },
{ l: de ? 'Audit-Sampling Rate' : 'Audit sampling rate', v: '5%' },
{ l: de ? 'False-Positive Rate' : 'False positive rate', v: '<2%' },
{ l: de ? 'Mean Latency (RAG)' : 'Mean latency (RAG)', v: '~180ms' },
{ l: de ? 'Throughput Control-Pipeline' : 'Throughput control pipeline', v: '~1.2k/min' },
].map((s, i) => (
<div key={i} style={{ display: 'flex', justifyContent: 'space-between', padding: '2mm 0', borderBottom: `1px solid ${COLORS.slate100}`, fontSize: '8.5pt' }}>
<span style={{ color: COLORS.slate700 }}>{s.l}</span>
<span style={{ fontWeight: 700, color: COLORS.indigo600, fontVariantNumeric: 'tabular-nums' }}>{s.v}</span>
</div>
))}
</div>
))}
</div>
<div>
<p style={{ fontSize: '9px', fontWeight: 700, color: COLORS.indigo, textTransform: 'uppercase', letterSpacing: '0.06em', margin: '0 0 6px' }}>{de ? 'Compliance-Engines' : 'Compliance Engines'}</p>
<ul style={{ margin: 0, paddingLeft: 0, listStyle: 'none' }}>
{agents.map(a => (
<li key={a} style={{ fontSize: '9px', color: COLORS.med, padding: '4px 0 4px 14px', position: 'relative', lineHeight: 1.45, borderBottom: `1px solid ${COLORS.border}` }}>
<span style={{ position: 'absolute', left: 0, top: '8px', width: '5px', height: '5px', borderRadius: '99px', background: COLORS.indigo, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
{a}
</li>
))}
</ul>
<p style={{ fontSize: '8px', color: COLORS.light, marginTop: '8px', fontStyle: 'italic' }}>
{de ? 'Wahrheit = Regeln + Evidenz · LLM = Übersetzer. Qdrant · BGE-M3 · MinIO. 100% EU-Cloud.' : 'Truth = Rules + Evidence · LLM = Translator. Qdrant · BGE-M3 · MinIO. 100% EU Cloud.'}
</p>
</div>
</div>
</div>
</PrintPage>
</Page>
)
}
const RISKS_DE = [
{ t: 'KI-Commoditisierung', sev: 'Hoch', sevColor: '#dc2626', timeline: '3-5 J.', d: 'LLMs senken Eintrittsbarrieren — Control-Generierung, DSFA-Erstellung, Policy-Templates werden Commodity.', m: 'Wir konkurrieren auf Layer 2-6: Integration, Auditierbarkeit, Workflows, EU-Hosting. KI ist Multiplikator, nicht Produkt.' },
{ t: 'US-Plattform-Expansion', sev: 'Mittel', sevColor: '#d97706', timeline: '2-4 J.', d: 'Microsoft Purview, Vanta oder Drata expandieren mit lokalisiertem EU-Angebot.', m: 'Struktureller Vorteil: 100% EU-Infrastruktur, kein US-SaaS, Betriebsrat-Fähigkeit. CLOUD Act ist Ausschlusskriterium.' },
{ t: 'Team-Risiko / Key-Person', sev: 'Mittel', sevColor: '#d97706', timeline: 'Jahr 1-2', d: 'Abhängigkeit von zwei Gründern in der Frühphase. Wissensverlust bei Ausfall.', m: 'Doku aller Prozesse in MkDocs. KI-Codebasis mit Tests. ESOP-Pool ab Hire 1. Frühe Einstellung Rechtsanwalt/DS.' },
{ t: 'Langsame Kundenakquise', sev: 'Mittel', sevColor: '#d97706', timeline: 'Jahr 1-3', d: 'B2B-Verkaufszyklen 3-9 Monate. Compliance-Budgets jährlich geplant.', m: 'Beratungsumsätze 5-30k/Mon überbrücken Anlauf. Channel (Bechtle/CANCOM) skaliert schneller. Land-and-Expand.' },
{ t: 'Regulatorische Änderungen', sev: 'Niedrig', sevColor: '#16a34a', timeline: 'Laufend', d: 'Neue EU-Gesetze erfordern Anpassung der Plattform.', m: 'Jede Änderung vergrößert unseren Markt. RAG-Pipeline indexiert neue Regularien in Tagen. 380+ schon im System.' },
{ t: 'Liquiditätsrisiko', sev: 'Niedrig', sevColor: '#16a34a', timeline: 'Jahr 1-2', d: 'Mit 200k Wandeldarlehen ist die Runway begrenzt. Ende 2027 nahe Null.', m: 'Organisches Wachstum durch Beratung. Break-Even 2029. Pre-Seed BW (L-Bank) verdoppelt Finanzierung auf 400k.' },
]
const RISKS_EN = [
{ t: 'AI Commoditization', sev: 'High', sevColor: '#dc2626', timeline: '3-5 yrs', d: 'LLMs lower entry barriers — control generation, DPIA creation, policy templates become commodity.', m: 'We compete on Layers 2-6: integration, auditability, workflows, EU hosting. AI is multiplier, not product.' },
{ t: 'US Platform Expansion', sev: 'Medium', sevColor: '#d97706', timeline: '2-4 yrs', d: 'Microsoft Purview, Vanta or Drata expand with localized EU offering.', m: 'Structural advantage: 100% EU infra, no US SaaS, works council compliance. CLOUD Act is a deal-breaker.' },
{ t: 'Team Risk / Key Person', sev: 'Medium', sevColor: '#d97706', timeline: 'Year 1-2', d: 'Dependency on two founders. Knowledge loss in case of absence.', m: 'All processes documented in MkDocs. AI-assisted codebase with tests. ESOP pool from hire 1. Early lawyer hire.' },
{ t: 'Slow Customer Acquisition', sev: 'Medium', sevColor: '#d97706', timeline: 'Year 1-3', d: 'B2B sales cycles 3-9 months. Compliance budgets planned annually.', m: 'Consulting revenue 5-30k/month bridges ramp. Channel (Bechtle/CANCOM) scales faster. Land-and-expand.' },
{ t: 'Regulatory Changes', sev: 'Low', sevColor: '#16a34a', timeline: 'Ongoing', d: 'New EU laws require platform adaptation.', m: 'Every change enlarges our market. RAG pipeline indexes new regulations in days. 380+ already in system.' },
{ t: 'Liquidity Risk', sev: 'Low', sevColor: '#16a34a', timeline: 'Year 1-2', d: 'With 200k convertible loan, runway is limited. Near zero by end of 2027.', m: 'Organic growth via consulting. Break-even 2029. Pre-Seed BW (L-Bank) doubles funding to 400k.' },
]
/* ===== RISKS ===== */
export function PrintRisksPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
const de = lang === 'de'
const risks = de ? RISKS_DE : RISKS_EN
const rows = de ? [
['Markt', 'Adoption langsamer als geplant', 'Mittel', 'Mittel', 'White-Glove-Onboarding, Channel über IHK/VDMA, Free-Tier für KMU <10 MA'],
['Markt', 'Vanta/Drata gehen EU-First', 'Niedrig', 'Hoch', 'BreakPilot-Vorsprung 18 Monate, Code-Security ist Moat, EU-Marken-DPMA + EUIPO gesichert'],
['Technologie', 'LLM-Anbieter (Qwen, DeepSeek) ändern Lizenz', 'Niedrig', 'Mittel', 'Multi-LLM-Strategie via LiteLLM, Offline-Modelle, eigene Embeddings'],
['Technologie', 'False-Positive in Code-Scans', 'Hoch', 'Niedrig', 'AI-Triage senkt False-Positives auf <2%, manueller Review-Loop, kontinuierliches Training'],
['Regulatorik', 'AI Act zwingt zu Konformitätsbewertung', 'Hoch', 'Mittel', 'Eigene Konformitätsbewertung im 2. Half 2026, sind selbst Hochrisiko-KI'],
['Regulatorik', 'Schrems III ändert EU-Datenfluss', 'Mittel', 'Niedrig', '100% EU-Hosting bereits umgesetzt, kein Datentransfer in USA'],
['Team', 'CTO-Abgang', 'Niedrig', 'Sehr hoch', 'CTO ist Gründer mit 37,3% Equity + Vesting, 4-Jahres-Bindung, Code-Doku exzellent'],
['Team', 'Senior-Engineers nicht skalierbar', 'Mittel', 'Mittel', 'Remote-First, Region Konstanz/Bodensee, Anthropic-/Google-Netzwerk des CTO'],
['Finanzen', 'Funding-Verzögerung', 'Mittel', 'Mittel', 'Gründerzuschuss aktiv, INVEST-Zuschuss vorbereitet, Wandeldarlehen-Bridge möglich'],
['Operativ', 'Compliance-Verstoß im eigenen Betrieb', 'Niedrig', 'Hoch', 'BreakPilot ist eigener Kunde, audit-ready by design, DSB extern bestellt'],
] : [
['Market', 'Adoption slower than planned', 'Medium', 'Medium', 'White-glove onboarding, channel via IHK/VDMA, free tier for SMEs <10 emp.'],
['Market', 'Vanta/Drata go EU-first', 'Low', 'High', 'BreakPilot lead 18 months, code security is moat, EU trademark DPMA + EUIPO secured'],
['Technology', 'LLM providers (Qwen, DeepSeek) change license', 'Low', 'Medium', 'Multi-LLM strategy via LiteLLM, offline models, own embeddings'],
['Technology', 'False positives in code scans', 'High', 'Low', 'AI triage lowers false positives to <2%, manual review loop, continuous training'],
['Regulatory', 'AI Act mandates conformity assessment', 'High', 'Medium', 'Own conformity assessment in 2nd half 2026, we are high-risk AI ourselves'],
['Regulatory', 'Schrems III changes EU data flow', 'Medium', 'Low', '100% EU hosting already in place, no data transfer to USA'],
['Team', 'CTO departure', 'Low', 'Very high', 'CTO is founder with 37.3% equity + vesting, 4-year tie-in, code documentation excellent'],
['Team', 'Senior engineers not scalable', 'Medium', 'Medium', "Remote-first, Konstanz/Bodensee region, CTO's Anthropic/Google network"],
['Finance', 'Funding delay', 'Medium', 'Medium', 'Founder grant active, INVEST grant prepared, convertible loan bridge possible'],
['Operations', 'Compliance violation in own operations', 'Low', 'High', 'BreakPilot is its own customer, audit-ready by design, external DPO appointed'],
]
return (
<PrintPage title={de ? 'Risiken & Mitigation' : 'Risks & Mitigation'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<SectionTitle subtitle={de ? 'Transparente Darstellung der wesentlichen Risiken und unserer Gegenmaßnahmen' : 'Transparent presentation of key risks and our countermeasures'}>
{de ? 'Risiken & Mitigation' : 'Risks & Mitigation'}
</SectionTitle>
<PrintTable
headers={[de ? 'Risiko' : 'Risk', de ? 'Schwere' : 'Severity', de ? 'Zeit' : 'Time', de ? 'Beschreibung' : 'Description', 'Mitigation']}
rows={risks.map(r => [
<strong key="t" style={{ color: COLORS.dark }}>{r.t}</strong>,
<span key="s" style={{ color: r.sevColor, fontWeight: 700 }}>{r.sev}</span>,
r.timeline,
r.d,
<span key="m" style={{ color: COLORS.med }}>{r.m}</span>,
])}
colWidths={['17%', '8%', '10%', '32%', '33%']}
<Page kicker="23" section={de ? 'RISIKEN & MITIGATION' : 'RISKS & MITIGATION'} title={de ? 'Bekannte Risiken, mit Mitigationen, die wir bereits umgesetzt haben.' : 'Known risks, with mitigations we have already implemented.'} subtitle={de ? 'Zehn Risiken in fünf Kategorien. Eintritts-Wahrscheinlichkeit × Auswirkung. Keine Show-Stopper.' : 'Ten risks across five categories. Probability × impact. No show-stoppers.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<DataTable
cols={[
{ header: de ? 'Kategorie' : 'Category', width: '11%' },
{ header: de ? 'Risiko' : 'Risk', width: '28%' },
{ header: de ? 'Wahrsch.' : 'Prob.', width: '10%' },
{ header: de ? 'Auswirkung' : 'Impact', width: '10%' },
{ header: de ? 'Mitigation' : 'Mitigation' },
]}
rows={rows}
dense
highlightFirstCol
/>
<div style={{ marginTop: '10px', padding: '10px 14px', background: '#f5f3ff', borderRadius: '6px', borderLeft: `3px solid ${COLORS.indigo}`, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<p style={{ fontSize: '10px', color: COLORS.med, margin: 0, fontStyle: 'italic', lineHeight: 1.5 }}>
<div style={{ marginTop: '5mm', flexShrink: 0 }}>
<Callout tone="positive" label={de ? 'Wir leben unsere eigene Medizin' : 'We eat our own dogfood'}>
{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>
? 'BreakPilot scannt seinen eigenen Code. Wir generieren unsere eigene VVT, eigene DSFA, eigene TOMs. Wir sind audit-ready bevor wir den ersten Kunden onboarden, und das ist die beste Mitigation für alle Compliance-bezogenen Risiken.'
: 'BreakPilot scans its own code. We generate our own RoPA, own DPIA, own TOMs. We are audit-ready before onboarding our first customer, and that is the best mitigation for all compliance-related risks.'}
</Callout>
</div>
</PrintPage>
</Page>
)
}
const GLOSSARY = {
de: [
{ cat: 'Code Security & DevSecOps', color: '#ef4444', terms: [['SAST', 'Static Application Security Testing — Quellcode-Analyse'], ['DAST', 'Dynamic Application Security Testing — Laufzeit-Tests'], ['SBOM', 'Software Bill of Materials — Komponenten-Liste'], ['SCA', 'Software Composition Analysis'], ['DevSecOps', 'Sicherheit integriert in Entwicklung'], ['CI/CD', 'Automatisierte Build/Deploy-Pipeline']] },
{ cat: 'Compliance & Datenschutz', color: COLORS.indigo, terms: [['DSGVO', 'EU-Datenschutzverordnung seit Mai 2018'], ['VVT', 'Verzeichnis von Verarbeitungstätigkeiten'], ['TOMs', 'Technisch-Organisatorische Maßnahmen'], ['DSFA', 'Datenschutz-Folgenabschätzung'], ['DSR', 'Betroffenenrechte (Auskunft, Löschung)'], ['DSB', 'Datenschutzbeauftragter'], ['ISMS', 'Information Security Management System']] },
{ cat: 'EU-Regulierungen', color: '#06b6d4', terms: [['AI Act', 'KI-Verordnung EU 2024/1689 — Risikoklassen'], ['CRA', 'Cyber Resilience Act — SBOM-Pflicht'], ['NIS2', 'EU-Cybersicherheits-Richtlinie'], ['MVO', 'Maschinenverordnung 2023/1230'], ['Cloud Act', 'US-Gesetz für extraterritorialen Datenzugriff'], ['FISA 702', 'US-Überwachungsgesetz'], ['BDSG', 'Bundesdatenschutzgesetz'], ['TISAX', 'Automotive Security Standard'], ['BSI', 'Bundesamt für Sicherheit in der IT']] },
{ cat: 'Kennzahlen', color: '#10b981', terms: [['ARR', 'Annual Recurring Revenue'], ['MRR', 'Monthly Recurring Revenue'], ['CAC', 'Customer Acquisition Cost'], ['LTV', 'Lifetime Value'], ['ARPU', 'Avg. Revenue Per User'], ['SaaS', 'Software as a Service'], ['ESOP', 'Employee Stock Option Plan'], ['ROI', 'Return on Investment']] },
{ cat: 'Technologie', color: '#f59e0b', terms: [['RAG', 'Retrieval Augmented Generation'], ['LLM', 'Large Language Model'], ['UCCA', 'Use-Case Compliance Assessment'], ['FRIA', 'Fundamental Rights Impact Assessment'], ['SDK', 'Software Development Kit'], ['OWASP', 'Open Web Application Security Project'], ['NIST', 'US-Standardisierungsbehörde'], ['ENISA', 'EU-Agentur für Cybersicherheit'], ['CE', 'EU-Konformitätskennzeichnung'], ['RFQ', 'Request for Quotation']] },
],
en: [
{ cat: 'Code Security & DevSecOps', color: '#ef4444', terms: [['SAST', 'Static Application Security Testing — source code'], ['DAST', 'Dynamic Application Security Testing — runtime'], ['SBOM', 'Software Bill of Materials — component list'], ['SCA', 'Software Composition Analysis'], ['DevSecOps', 'Security integrated into development'], ['CI/CD', 'Automated build/deploy pipeline']] },
{ cat: 'Compliance & Data Protection', color: COLORS.indigo, terms: [['GDPR', 'EU data protection regulation since May 2018'], ['RoPA', 'Record of Processing Activities'], ['TOMs', 'Technical & Organizational Measures'], ['DPIA', 'Data Protection Impact Assessment'], ['DSR', 'Data subject rights (access, erasure)'], ['DPO', 'Data Protection Officer'], ['ISMS', 'Information Security Management System']] },
{ cat: 'EU Regulations', color: '#06b6d4', terms: [['AI Act', 'AI Regulation EU 2024/1689 — risk classes'], ['CRA', 'Cyber Resilience Act — SBOM mandatory'], ['NIS2', 'EU cybersecurity directive'], ['MVO', 'Machinery Regulation 2023/1230'], ['Cloud Act', 'US law for extraterritorial data access'], ['FISA 702', 'US surveillance law'], ['BDSG', 'German Federal Data Protection Act'], ['TISAX', 'Automotive security standard'], ['BSI', 'German Federal Office for IT Security']] },
{ cat: 'Business Metrics', color: '#10b981', terms: [['ARR', 'Annual Recurring Revenue'], ['MRR', 'Monthly Recurring Revenue'], ['CAC', 'Customer Acquisition Cost'], ['LTV', 'Lifetime Value'], ['ARPU', 'Avg. Revenue Per User'], ['SaaS', 'Software as a Service'], ['ESOP', 'Employee Stock Option Plan'], ['ROI', 'Return on Investment']] },
{ cat: 'Technology', color: '#f59e0b', terms: [['RAG', 'Retrieval Augmented Generation'], ['LLM', 'Large Language Model'], ['UCCA', 'Use-Case Compliance Assessment'], ['FRIA', 'Fundamental Rights Impact Assessment'], ['SDK', 'Software Development Kit'], ['OWASP', 'Open Web Application Security Project'], ['NIST', 'US standards body'], ['ENISA', 'EU Agency for Cybersecurity'], ['CE', 'EU conformity marking'], ['RFQ', 'Request for Quotation']] },
],
}
/* ===== GLOSSARY ===== */
const GLOSSARY: [string, string][] = [
['AI Act', 'EU-Verordnung 2024/1689. Klassifiziert KI-Systeme in 4 Risikoklassen. Hochrisiko-KI benötigt DSFA + Konformitätsbewertung.'],
['Audit-Trail', 'Lückenlose Nachverfolgung jeder Compliance-relevanten Änderung, wer, was, wann, warum. Auditor-tauglich.'],
['BSI-C5', 'Cloud Computing Compliance Criteria Catalogue des BSI. Standard für Cloud-Sicherheit in DE.'],
['BSI-KritisV', 'KRITIS-Verordnung. Definiert kritische Infrastrukturen, die NIS2-Maßnahmen umsetzen müssen.'],
['CE-Kennzeichnung', 'Conformité Européenne. Pflicht für Produkte im EU-Binnenmarkt. Software-Anteil wird seit MaschVO 2023 bewertet.'],
['CRA', 'Cyber Resilience Act (VO 2024/2847). Verbindlich ab Dez 2027 für Produkte mit digitalen Elementen.'],
['DAST', 'Dynamic Application Security Testing. Prüft laufende Apps gegen Angriffe.'],
['DORA', 'Digital Operational Resilience Act (VO 2022/2554). Für Finanzsektor seit Jan 2025.'],
['DSFA', 'Datenschutz-Folgenabschätzung (Art. 35 DSGVO). Pflicht bei hohem Risiko für Betroffene.'],
['DSR', 'Data Subject Request. Betroffenenrechte (Auskunft, Berichtigung, Löschung, Portabilität).'],
['EUIPO', 'European Union Intellectual Property Office. EU-weite Markenanmeldung.'],
['FISA 702', 'US-Gesetz zur Überwachung ausländischer Daten, gilt extraterritorial für US-Unternehmen.'],
['INVEST-Zuschuss', 'BMWi-Förderung: 20% des Investments rückzahlungsfrei (bis €100k pro Investor / Jahr).'],
['LiteLLM', 'Open-Source-Proxy für OpenAI-kompatible APIs. Failover, Rate-Limiting, PII-Filter.'],
['Lines of Code (LoC)', 'Quelltextzeilen ohne Leerzeilen + Kommentare. Indikator für Engineering-Volumen.'],
['MaschVO', 'EU-Maschinenverordnung 2023/1230. Verbindlich ab Jan 2027.'],
['MCP', 'Model Context Protocol. Anthropic-Standard für Tool-Integration in LLMs.'],
['MDR', 'Medical Device Regulation (VO 2017/745). Für Medizinprodukte inkl. Software.'],
['NIS2', 'Network and Information Security Directive 2 (RL 2022/2555). Verbindlich seit Okt 2024 für ~30k DE-Unternehmen.'],
['OPA', 'Open Policy Agent. Policy-as-Code für Autorisierung und Compliance-Regeln.'],
['RAG', 'Retrieval-Augmented Generation. LLM mit Zugriff auf externe Wissensbasis.'],
['RFQ', 'Request for Quotation. Anfrage zur Angebotsabgabe.'],
['SAST', 'Static Application Security Testing. Code-Analyse ohne Ausführung.'],
['SBOM', 'Software Bill of Materials. Inventar aller Komponenten + Lizenzen. CRA-Pflicht ab 2027.'],
['Schrems II', 'EuGH-Urteil C-311/18 (2020). Erklärte EU-US Privacy Shield für ungültig.'],
['Self-Hosted', 'Software, die der Kunde auf eigener Infrastruktur betreibt, kein externer SaaS.'],
['TISAX', 'Trusted Information Security Assessment Exchange. Automotive-Sicherheitsstandard.'],
['TOM', 'Technische und organisatorische Maßnahmen (Art. 32 DSGVO).'],
['VVT', 'Verzeichnis von Verarbeitungstätigkeiten (Art. 30 DSGVO). Pflicht ab 250 Mitarbeitern.'],
['Wandeldarlehen', 'Convertible Loan. Hybrid-Instrument zwischen Kredit und Equity. SAFE-ähnlich.'],
]
export function PrintGlossaryPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
const de = lang === 'de'
const cats = de ? GLOSSARY.de : GLOSSARY.en
const half = Math.ceil(GLOSSARY.length / 2)
const left = GLOSSARY.slice(0, half)
const right = GLOSSARY.slice(half)
return (
<PrintPage title={de ? 'Glossar' : 'Glossary'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<SectionTitle subtitle={de ? 'Schlüsselbegriffe aus Security, Compliance, Regulatorik und SaaS-Metriken' : 'Key terms across security, compliance, regulation and SaaS metrics'}>
{de ? 'Anhang · Glossar & Abkürzungen' : 'Appendix · Glossary & Abbreviations'}
</SectionTitle>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '8px', flex: 1 }}>
{cats.map(c => (
<div key={c.cat} style={{ border: `1px solid ${COLORS.border}`, borderRadius: '6px', padding: '8px 10px' }}>
<p style={{ fontSize: '9px', fontWeight: 700, color: c.color, textTransform: 'uppercase', letterSpacing: '0.04em', margin: '0 0 4px' }}>{c.cat}</p>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<tbody>
{c.terms.map(([abbr, desc]) => (
<tr key={abbr}>
<td style={{ fontSize: '8px', fontWeight: 700, color: c.color, padding: '2px 6px 2px 0', verticalAlign: 'top', whiteSpace: 'nowrap', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>{abbr}</td>
<td style={{ fontSize: '8px', color: COLORS.med, padding: '2px 0', lineHeight: 1.4 }}>{desc}</td>
</tr>
))}
</tbody>
</table>
<Page kicker="24" section={de ? 'ANHANG · GLOSSAR' : 'APPENDIX · GLOSSARY'} title={de ? 'Begriffsdefinitionen, alle Akronyme und Fachterme in diesem Dokument.' : 'Term definitions, all acronyms and technical terms in this document.'} subtitle={de ? '30 Begriffe, Compliance, Regulatorik, Engineering, Finanzierung.' : '30 terms, compliance, regulation, engineering, financing.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8mm', fontSize: '8pt' }}>
{[left, right].map((col, ci) => (
<div key={ci}>
{col.map((g, i) => (
<div key={i} style={{ display: 'grid', gridTemplateColumns: '22mm 1fr', gap: '3mm', padding: '1.5mm 0', borderBottom: `1px solid ${COLORS.slate100}` }}>
<div style={{ fontWeight: 700, color: COLORS.indigo600, fontSize: '8pt' }}>{g[0]}</div>
<div style={{ color: COLORS.slate700, lineHeight: 1.4 }}>{g[1]}</div>
</div>
))}
</div>
))}
</div>
</PrintPage>
</Page>
)
}
@@ -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 (
<div>
{(title || yAxisHint) && (
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: '2mm' }}>
{title && <div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.slate600, textTransform: 'uppercase', letterSpacing: '0.1em' }}>{title}</div>}
{yAxisHint && <div style={{ fontSize: '7pt', color: COLORS.slate400 }}>{yAxisHint}</div>}
</div>
)}
<div style={{ display: 'flex', alignItems: 'stretch', gap: '4mm', position: 'relative' }}>
{/* Y-axis ticks */}
<div style={{ width: '14mm', display: 'flex', flexDirection: 'column', justifyContent: 'space-between', height: `${height}mm`, fontSize: '6.5pt', color: COLORS.slate400, textAlign: 'right', fontVariantNumeric: 'tabular-nums' }}>
{ticks.slice().reverse().map((t, i) => (
<div key={i}>{fmt(t)}</div>
))}
</div>
{/* Bars + grid */}
<div style={{ flex: 1, position: 'relative', height: `${height}mm`, borderLeft: `1px solid ${COLORS.slate300}`, borderBottom: `1px solid ${COLORS.slate300}` }}>
{/* Grid lines */}
{ticks.slice(1).map((_, i) => (
<div key={i} style={{ position: 'absolute', left: 0, right: 0, top: `${(1 - (i + 1) / 4) * 100}%`, height: '0.5pt', background: COLORS.slate100, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
))}
{/* Bars */}
<div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'flex-end', gap: '3mm', padding: '0 2mm' }}>
{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 (
<div key={i} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'flex-end', height: '100%', position: 'relative' }}>
<div style={{ fontSize: '7pt', fontWeight: 700, color: color, marginBottom: '0.8mm', fontVariantNumeric: 'tabular-nums', whiteSpace: 'nowrap' }}>{fmt(d.value)}</div>
<div style={{ width: '100%', height: `${h}%`, minHeight: '1pt', background: color, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
</div>
)
})}
</div>
</div>
</div>
{/* X-axis labels */}
<div style={{ display: 'flex', gap: '3mm', paddingLeft: '18mm', paddingRight: '2mm', marginTop: '1mm' }}>
{data.map((d, i) => (
<div key={i} style={{ flex: 1, fontSize: '7pt', color: COLORS.slate500, textAlign: 'center', fontWeight: 600, fontVariantNumeric: 'tabular-nums' }}>{d.label}</div>
))}
</div>
</div>
)
}
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 (
<div>
{title && <div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.slate600, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '2mm' }}>{title}</div>}
<div style={{ position: 'relative', height: `${height}mm`, borderLeft: `1px solid ${COLORS.slate300}`, borderBottom: `1px solid ${COLORS.slate300}` }}>
<svg viewBox="0 0 100 100" preserveAspectRatio="none" style={{ position: 'absolute', inset: 0, width: '100%', height: '100%' }}>
{/* Grid */}
{[0.25, 0.5, 0.75].map(t => (
<line key={t} x1="0" x2="100" y1={t * 100} y2={t * 100} stroke={COLORS.slate100} strokeWidth="0.3" />
))}
{fill && <path d={areaD} fill={color} fillOpacity="0.12" />}
<path d={pathD} fill="none" stroke={color} strokeWidth="0.6" strokeLinejoin="round" strokeLinecap="round" />
{points.map((p, i) => (
<circle key={i} cx={p.x} cy={p.y} r="1.2" fill={color} />
))}
</svg>
{/* Value labels above each point */}
<div style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>
{points.map((p, i) => (
<div key={i} style={{ position: 'absolute', left: `${p.x}%`, top: `${p.y}%`, transform: 'translate(-50%, -120%)', fontSize: '6.5pt', fontWeight: 700, color, whiteSpace: 'nowrap', fontVariantNumeric: 'tabular-nums' }}>{fmt(p.v)}</div>
))}
</div>
</div>
{/* X labels */}
<div style={{ display: 'flex', marginTop: '1mm' }}>
{points.map((p, i) => (
<div key={i} style={{ flex: 1, fontSize: '7pt', color: COLORS.slate500, textAlign: 'center', fontWeight: 600, fontVariantNumeric: 'tabular-nums' }}>{p.label}</div>
))}
</div>
</div>
)
}
/** 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 (
<div style={{ display: 'flex', flexDirection: 'column', gap: '3mm' }}>
{rows.map((row, i) => (
<div key={i}>
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.slate700, marginBottom: '1.5mm' }}>{row.label}</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '1mm' }}>
{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 (
<div key={j} style={{ display: 'flex', alignItems: 'center', gap: '3mm' }}>
<div style={{ flex: 1, position: 'relative', height: '4mm', background: COLORS.slate50, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<div style={{ position: 'absolute', left: 0, top: 0, bottom: 0, width: `${w}%`, background: color, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
{b.cap && <div style={{ position: 'absolute', left: '2mm', top: 0, bottom: 0, display: 'flex', alignItems: 'center', fontSize: '7pt', color: '#ffffff', fontWeight: 700, letterSpacing: '0.04em' }}>{b.cap}</div>}
</div>
<div style={{ width: '20mm', textAlign: 'right', fontSize: '9pt', fontWeight: 800, color, fontVariantNumeric: 'tabular-nums' }}>{fmt(b.value)}</div>
</div>
)
})}
</div>
</div>
))}
</div>
)
}
/** 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 (
<svg viewBox="0 0 100 100" style={{ width: `${size}mm`, height: `${size}mm`, display: 'block' }}>
{arcs.map((a, i) => (
<path key={i} d={a.d} fill={a.color} />
))}
</svg>
)
}
/** Progress meter (0-100%) — horizontal */
export function ProgressBar({ pct, color = COLORS.indigo600, label, value }: { pct: number; color?: string; label?: string; value?: string }) {
return (
<div>
{(label || value) && (
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: '1mm', fontSize: '8pt' }}>
{label && <span style={{ color: COLORS.slate700, fontWeight: 500 }}>{label}</span>}
{value && <span style={{ color: COLORS.slate900, fontWeight: 700, fontVariantNumeric: 'tabular-nums' }}>{value}</span>}
</div>
)}
<div style={{ height: '3mm', background: COLORS.slate100, position: 'relative', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<div style={{ position: 'absolute', left: 0, top: 0, bottom: 0, width: `${Math.min(100, Math.max(0, pct))}%`, background: color, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
</div>
</div>
)
}
/** 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 (
<div>
{/* TAM outer */}
<div style={{ border: `1px solid ${COLORS.slate300}`, padding: '5mm', position: 'relative' }}>
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: '1.5mm' }}>
<span style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.14em' }}>TAM &middot; {tam.label}</span>
{tam.growth != null && <span style={{ fontSize: '7.5pt', color: COLORS.slate500, fontWeight: 600 }}>+{tam.growth}% p.a.</span>}
</div>
<div style={{ fontSize: '32pt', fontWeight: 800, color: COLORS.slate900, lineHeight: 1, letterSpacing: '-0.025em', fontVariantNumeric: 'tabular-nums' }}>{fmt(tam.value)}</div>
{tam.note && <div style={{ fontSize: '8pt', color: COLORS.slate600, marginTop: '2mm', maxWidth: '120mm' }}>{tam.note}</div>}
{/* SAM inner */}
<div style={{ marginTop: '4mm', marginLeft: '10mm', border: `1px solid ${COLORS.indigo600}`, background: COLORS.indigo50, padding: '4mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: '1.5mm' }}>
<span style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.indigo700, textTransform: 'uppercase', letterSpacing: '0.14em' }}>SAM &middot; {sam.label}</span>
{sam.growth != null && <span style={{ fontSize: '7.5pt', color: COLORS.indigo700, fontWeight: 600 }}>+{sam.growth}% p.a. &middot; {Math.round(samPct * 100)}% TAM</span>}
</div>
<div style={{ fontSize: '26pt', fontWeight: 800, color: COLORS.indigo700, lineHeight: 1, letterSpacing: '-0.02em', fontVariantNumeric: 'tabular-nums' }}>{fmt(sam.value)}</div>
{sam.note && <div style={{ fontSize: '8pt', color: COLORS.slate700, marginTop: '2mm' }}>{sam.note}</div>}
{/* SOM inner-inner */}
<div style={{ marginTop: '3mm', marginLeft: '8mm', border: `2px solid ${COLORS.emerald600}`, background: COLORS.emerald50, padding: '4mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: '1.5mm' }}>
<span style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.emerald700, textTransform: 'uppercase', letterSpacing: '0.14em' }}>SOM &middot; {som.label}</span>
{som.growth != null && <span style={{ fontSize: '7.5pt', color: COLORS.emerald700, fontWeight: 600 }}>+{som.growth}% p.a. &middot; {(somPct * 100).toFixed(1)}% TAM</span>}
</div>
<div style={{ fontSize: '24pt', fontWeight: 800, color: COLORS.emerald700, lineHeight: 1, letterSpacing: '-0.02em', fontVariantNumeric: 'tabular-nums' }}>{fmt(som.value)}</div>
{som.note && <div style={{ fontSize: '8pt', color: COLORS.slate700, marginTop: '2mm' }}>{som.note}</div>}
</div>
</div>
</div>
</div>
)
}
@@ -0,0 +1,190 @@
import { Language } from '@/lib/types'
import { Page, MatrixGlyph, COLORS, Callout, Glyph } from './PrintLayout'
import {
EXTENDED_COMPETITORS,
ALL_FEATURES,
APPSEC_COMPETITORS,
APPSEC_FEATURES,
GROUP_LABELS,
DACH_NOTE,
} from '@/components/slides/CompetitionSlide.data'
interface SlideBase { lang: Language; pageNum: number; totalPages: number; versionName: string }
/* ===== COMPETITION, PAGE 1: Compliance space ===== */
export function PrintCompetitionPage1({ lang, pageNum, totalPages, versionName }: SlideBase) {
const de = lang === 'de'
// group features by category
const grouped = ALL_FEATURES.reduce<Record<string, typeof ALL_FEATURES>>((acc, f) => {
const g = f.group || 'other'
if (!acc[g]) acc[g] = []
acc[g].push(f)
return acc
}, {})
const groupOrder = ['code-security', 'ai-data', 'frameworks', 'documentation', 'operations', 'platform', 'industry']
const competitorCols = ['vanta', 'drata', 'sprinto', 'proliance', 'dataguard', 'heydata'] as const
type CC = typeof competitorCols[number]
const competitorLabels: Record<CC, string> = {
vanta: 'V', drata: 'D', sprinto: 'S', proliance: 'P', dataguard: 'DG', heydata: 'H',
}
return (
<Page kicker="12" section={de ? 'WETTBEWERB · 1 / 2, COMPLIANCE' : 'COMPETITION · 1 / 2, COMPLIANCE'} title={de ? 'Compliance-Markt: keiner kombiniert DSGVO + Code-Security + Self-Hosted KI.' : 'Compliance market: no one combines GDPR + code security + self-hosted AI.'} subtitle={de ? '6 globale/lokale Wettbewerber × 45 Features. ★ markiert eindeutige BreakPilot-USPs.' : '6 global/local competitors × 45 features. ★ marks unique BreakPilot USPs.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName} footnote={de ? DACH_NOTE.de : DACH_NOTE.en}>
{/* Competitor profile table */}
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '7.5pt', fontVariantNumeric: 'tabular-nums', marginBottom: '4mm' }}>
<thead>
<tr>
{[de ? 'Wettbewerber' : 'Competitor', 'HQ', de ? 'Gegr.' : 'Founded', 'MA', 'ARR', de ? 'Kunden' : 'Customers', de ? 'Funding' : 'Funding', 'AI', 'Pricing'].map((h, i) => (
<th key={i} style={{ background: COLORS.indigo50, color: COLORS.indigo700, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.06em', fontSize: '6.5pt', padding: '1.5mm 2mm', textAlign: i >= 2 ? 'right' : 'left', borderBottom: `1px solid ${COLORS.slate300}`, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>{h}</th>
))}
</tr>
</thead>
<tbody>
{EXTENDED_COMPETITORS.map((c, i) => (
<tr key={c.name} style={{ background: i % 2 === 1 ? COLORS.slate50 : 'transparent', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<td style={{ padding: '1.5mm 2mm', fontWeight: 700, color: COLORS.slate900 }}>{c.flag} {c.name}</td>
<td style={{ padding: '1.5mm 2mm', color: COLORS.slate700 }}>{c.hqCountry}</td>
<td style={{ padding: '1.5mm 2mm', textAlign: 'right', color: COLORS.slate700 }}>{c.founded}</td>
<td style={{ padding: '1.5mm 2mm', textAlign: 'right', color: COLORS.slate700 }}>{c.employees.toLocaleString('de-DE')}</td>
<td style={{ padding: '1.5mm 2mm', textAlign: 'right', color: COLORS.slate800, fontWeight: 600 }}>{c.revenue}</td>
<td style={{ padding: '1.5mm 2mm', textAlign: 'right', color: COLORS.slate700 }}>{c.customers.toLocaleString('de-DE')}</td>
<td style={{ padding: '1.5mm 2mm', textAlign: 'right', color: COLORS.slate700, fontSize: '6.5pt' }}>{c.fundingTotal}</td>
<td style={{ padding: '1.5mm 2mm', textAlign: 'right' }}>
<MatrixGlyph v={c.aiUsage === 'full' ? true : c.aiUsage === 'partial' ? 'partial' : false} />
</td>
<td style={{ padding: '1.5mm 2mm', textAlign: 'right', color: COLORS.slate700, fontSize: '6.5pt' }}>{c.pricing}</td>
</tr>
))}
</tbody>
</table>
{/* Feature matrix */}
<div style={{ flex: 1, minHeight: 0, overflow: 'hidden' }}>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '6.5pt', fontVariantNumeric: 'tabular-nums' }}>
<thead>
<tr>
<th style={{ background: COLORS.slate900, color: '#ffffff', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.06em', fontSize: '6pt', padding: '1.2mm 2mm', textAlign: 'left', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>{de ? 'Feature (45)' : 'Feature (45)'}</th>
<th style={{ background: COLORS.indigo600, color: '#ffffff', fontWeight: 700, fontSize: '6pt', padding: '1.2mm 1mm', textAlign: 'center', width: '7mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>BP</th>
{competitorCols.map(k => (
<th key={k} style={{ background: COLORS.slate800, color: '#ffffff', fontWeight: 700, fontSize: '6pt', padding: '1.2mm 1mm', textAlign: 'center', width: '7mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>{competitorLabels[k]}</th>
))}
<th style={{ background: COLORS.slate100, color: COLORS.slate700, fontWeight: 700, fontSize: '5.5pt', padding: '1.2mm 1mm', textAlign: 'center', width: '12mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>USP</th>
</tr>
</thead>
<tbody>
{groupOrder.flatMap(g => {
const groupLabel = GROUP_LABELS[g]
const features = grouped[g] || []
if (!features.length) return []
return [
<tr key={`g-${g}`}>
<td colSpan={9} style={{ background: COLORS.slate100, padding: '1mm 2mm', fontSize: '5.5pt', fontWeight: 700, color: COLORS.slate600, textTransform: 'uppercase', letterSpacing: '0.1em', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
{de ? groupLabel.de : groupLabel.en}
</td>
</tr>,
...features.map((f, i) => (
<tr key={`${g}-${i}`} style={{ background: i % 2 === 1 ? COLORS.slate50 : 'transparent', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<td style={{ padding: '0.7mm 2mm', color: COLORS.slate800, fontSize: '6.5pt', lineHeight: 1.25 }}>{de ? f.de : f.en}</td>
<td style={{ padding: '0.7mm 1mm', textAlign: 'center' }}><MatrixGlyph v={f.bp} isUSP={f.isUSP} /></td>
{competitorCols.map(k => (
<td key={k} style={{ padding: '0.7mm 1mm', textAlign: 'center' }}><MatrixGlyph v={f[k] as Glyph} /></td>
))}
<td style={{ padding: '0.7mm 1mm', textAlign: 'center', fontSize: '6pt', color: COLORS.indigo600, fontWeight: 700 }}>{f.isUSP ? '★' : ''}</td>
</tr>
)),
]
})}
</tbody>
</table>
</div>
{/* Legend */}
<div style={{ flexShrink: 0, marginTop: '2mm', display: 'flex', gap: '6mm', fontSize: '6.5pt', color: COLORS.slate500 }}>
<span><MatrixGlyph v={true} /> {de ? 'voll' : 'full'}</span>
<span><MatrixGlyph v="partial" /> {de ? 'teilw.' : 'partial'}</span>
<span><MatrixGlyph v={false} /> {de ? 'fehlt' : 'missing'}</span>
<span> <span style={{ color: COLORS.indigo600, fontWeight: 700 }}>{de ? 'BreakPilot-USP' : 'BreakPilot USP'}</span></span>
<span style={{ flex: 1 }} />
<span>V=Vanta · D=Drata · S=Sprinto · P=Proliance · DG=DataGuard · H=heyData</span>
</div>
</Page>
)
}
/* ===== COMPETITION, PAGE 2: AppSec space ===== */
export function PrintCompetitionPage2({ lang, pageNum, totalPages, versionName }: SlideBase) {
const de = lang === 'de'
return (
<Page kicker="12" section={de ? 'WETTBEWERB · 2 / 2, APPSEC' : 'COMPETITION · 2 / 2, APPSEC'} title={de ? 'Application Security: BreakPilot deckt SAST + DAST + SCA + Pentesting in einer Plattform ab.' : 'Application Security: BreakPilot covers SAST + DAST + SCA + pentesting in one platform.'} subtitle={de ? 'Acht etablierte AppSec-Anbieter, keiner kombiniert SAST + DAST + Auto-Fix + Self-Hosted für KMU.' : 'Eight established AppSec vendors, none combines SAST + DAST + auto-fix + self-hosted for SMEs.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
{/* Competitor profile table */}
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '7.5pt', fontVariantNumeric: 'tabular-nums', marginBottom: '4mm' }}>
<thead>
<tr>
{[de ? 'Anbieter' : 'Vendor', 'HQ', de ? 'Gegr.' : 'Founded', 'MA', 'ARR', de ? 'Kunden' : 'Customers', de ? 'Pricing' : 'Pricing', de ? 'Fokus' : 'Focus'].map((h, i) => (
<th key={i} style={{ background: COLORS.indigo50, color: COLORS.indigo700, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.06em', fontSize: '6.5pt', padding: '1.5mm 2mm', textAlign: i >= 2 && i <= 6 ? 'right' : 'left', borderBottom: `1px solid ${COLORS.slate300}`, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>{h}</th>
))}
</tr>
</thead>
<tbody>
{APPSEC_COMPETITORS.map((c, i) => (
<tr key={c.name} style={{ background: i % 2 === 1 ? COLORS.slate50 : 'transparent', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<td style={{ padding: '1.5mm 2mm', fontWeight: 700, color: COLORS.slate900 }}>{c.flag} {c.name}</td>
<td style={{ padding: '1.5mm 2mm', color: COLORS.slate700 }}>{c.hq}</td>
<td style={{ padding: '1.5mm 2mm', textAlign: 'right', color: COLORS.slate700 }}>{c.founded}</td>
<td style={{ padding: '1.5mm 2mm', textAlign: 'right', color: COLORS.slate700 }}>{c.employees.toLocaleString('de-DE')}</td>
<td style={{ padding: '1.5mm 2mm', textAlign: 'right', color: COLORS.slate800, fontWeight: 600 }}>{c.revenue}</td>
<td style={{ padding: '1.5mm 2mm', textAlign: 'right', color: COLORS.slate700, fontSize: '6.5pt' }}>{c.customers}</td>
<td style={{ padding: '1.5mm 2mm', textAlign: 'right', color: COLORS.slate700, fontSize: '6.5pt' }}>{c.pricing}</td>
<td style={{ padding: '1.5mm 2mm', color: COLORS.slate600, fontSize: '6.5pt', lineHeight: 1.3 }}>{de ? c.focus.de : c.focus.en}</td>
</tr>
))}
</tbody>
</table>
{/* Feature matrix */}
<div style={{ flex: 1, minHeight: 0 }}>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '7pt', fontVariantNumeric: 'tabular-nums' }}>
<thead>
<tr>
<th style={{ background: COLORS.slate900, color: '#ffffff', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.06em', fontSize: '6.5pt', padding: '1.5mm 2mm', textAlign: 'left', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>Feature</th>
<th style={{ background: COLORS.indigo600, color: '#ffffff', fontWeight: 700, fontSize: '6.5pt', padding: '1.5mm 1mm', textAlign: 'center', width: '8%', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>BP</th>
{['Snyk', 'Veracode', 'Checkmarx', 'Sonar', 'Semgrep', 'Pentera', 'Invicti', 'Intruder'].map(name => (
<th key={name} style={{ background: COLORS.slate800, color: '#ffffff', fontWeight: 700, fontSize: '6pt', padding: '1.5mm 1mm', textAlign: 'center', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>{name}</th>
))}
</tr>
</thead>
<tbody>
{APPSEC_FEATURES.map((f, i) => (
<tr key={i} style={{ background: i % 2 === 1 ? COLORS.slate50 : 'transparent', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<td style={{ padding: '1.5mm 2mm', color: COLORS.slate800, fontSize: '7pt', lineHeight: 1.3, fontWeight: 500 }}>{de ? f.de : f.en}</td>
<td style={{ padding: '1.5mm 1mm', textAlign: 'center' }}><MatrixGlyph v={f.bp} /></td>
<td style={{ padding: '1.5mm 1mm', textAlign: 'center' }}><MatrixGlyph v={f.snyk} /></td>
<td style={{ padding: '1.5mm 1mm', textAlign: 'center' }}><MatrixGlyph v={f.veracode} /></td>
<td style={{ padding: '1.5mm 1mm', textAlign: 'center' }}><MatrixGlyph v={f.checkmarx} /></td>
<td style={{ padding: '1.5mm 1mm', textAlign: 'center' }}><MatrixGlyph v={f.sonar} /></td>
<td style={{ padding: '1.5mm 1mm', textAlign: 'center' }}><MatrixGlyph v={f.semgrep} /></td>
<td style={{ padding: '1.5mm 1mm', textAlign: 'center' }}><MatrixGlyph v={f.pentera} /></td>
<td style={{ padding: '1.5mm 1mm', textAlign: 'center' }}><MatrixGlyph v={f.invicti} /></td>
<td style={{ padding: '1.5mm 1mm', textAlign: 'center' }}><MatrixGlyph v={f.intruder} /></td>
</tr>
))}
</tbody>
</table>
</div>
<div style={{ marginTop: '5mm', flexShrink: 0 }}>
<Callout tone="accent" label={de ? 'Was uns differenziert' : 'What differentiates us'}>
{de
? 'AppSec-Tools liefern Findings. BreakPilot liefert Findings + Auto-Fix-Vorschläge + Compliance-Dokumentation + Audit-Trail, alles auf einer EU-souveränen Plattform. Snyk + Vanta zusammen kosten 4-6× mehr und bleiben US-gehostet.'
: 'AppSec tools deliver findings. BreakPilot delivers findings + auto-fix proposals + compliance documentation + audit trail, all on one EU-sovereign platform. Snyk + Vanta together cost 4-6× more and remain US-hosted.'}
</Callout>
</div>
</Page>
)
}
@@ -1,301 +0,0 @@
import { PrintPage, SectionTitle, PrintTable, Badge, COLORS } from './PrintLayout'
import { Language, PitchCompany, PitchFunding, PitchProduct, PitchMarket, PitchTeamMember, PitchMilestone } from '@/lib/types'
const DE_PROBLEM_CARDS = [
{ title: 'KI-Dilemma', stat: 'Abgehängt', desc: 'Produzierende Unternehmen brauchen KI, um wettbewerbsfähig zu bleiben. Aber US-KI 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 und verstößt gegen europäisches Recht.' },
{ title: 'Patriot Act + FISA 702', stat: 'Kein Schutz', 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: '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 Compliance-Kosten, die US- und Asien-Konkurrenten nicht haben. KMU können das nicht mehr allein stemmen.' },
]
const EN_PROBLEM_CARDS = [
{ title: 'AI Dilemma', stat: 'Left Behind', desc: 'Manufacturing companies need AI to stay competitive. But letting US AI 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 and may violate European law.' },
{ title: 'Patriot Act + FISA 702', stat: 'No Protection', 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: '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. SMEs can no longer handle this alone.' },
]
const DE_PILLARS = [
{ title: 'Kontinuierliche Code-Security', 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. Kein manueller Aufwand, keine vergessenen Schwachstellen.' },
{ title: 'Compliance auf Autopilot', desc: 'VVT, TOMs, DSFA, Löschfristen, CE-Risikobeurteilung automatisch generiert. Nach dem Audit: Haupt- und Nebenabweichungen End-to-End — Rollen zuweisen, Stichtage, Tickets, Nachweise einfordern, Eskalation an GF. Kein Excel, kein Hinterherlaufen, kein Stressaudit.' },
{ title: 'Deutsche Cloud, volle Integration', desc: 'BSI-zertifizierte Cloud in Deutschland. Live-Support über Jitsi (Video) und Matrix (Chat). Keine US-SaaS im Source Code — DSGVO-konform by design. Optional: Mac Mini/Studio für maximale Privacy bei Kleinstunternehmen. Nahtlose Integration in bestehende Workflows.' },
]
const EN_PILLARS = [
{ title: 'Continuous Code Security', 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. No manual effort, no forgotten vulnerabilities.' },
{ title: 'Compliance on Autopilot', desc: 'RoPA, TOMs, DPIA, retention policies, CE risk assessment generated automatically. Post-audit: major and minor deviations end-to-end — role assignment, deadlines, tickets, evidence collection, escalation to management. No Excel, no chasing, no stress audits.' },
{ title: 'German Cloud, Full Integration', desc: 'BSI-certified cloud in Germany. Live support via Jitsi (video) and Matrix (chat). No US SaaS in source code — GDPR compliant by design. Optional: Mac Mini/Studio for maximum privacy for micro businesses. Seamless integration into existing workflows.' },
]
function fmtEur(n: number) { return n.toLocaleString('de-DE', { maximumFractionDigits: 0 }) + ' EUR' }
interface SlideBase { lang: Language; pageNum: number; totalPages: number; versionName: string }
export function PrintCoverPage({ company, funding, versionName, lang }: { company: PitchCompany; funding: PitchFunding; lang: Language; versionName: string }) {
const de = lang === 'de'
const instrument = funding?.instrument || 'Pre-Seed'
return (
<div className="print-page-break">
<div className="print-page" style={{ width: '297mm', height: '210mm', backgroundColor: '#ffffff', display: 'flex', flexDirection: 'column', fontFamily: 'system-ui, -apple-system, sans-serif', boxSizing: 'border-box', margin: '0 auto 32px', boxShadow: '0 4px 24px rgba(0,0,0,0.12)', overflow: 'hidden' }}>
<div style={{ height: '80px', background: 'linear-gradient(135deg, #4f46e5, #7c3aed)', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact', display: 'flex', alignItems: 'center', padding: '0 32px', gap: '16px', flexShrink: 0 }}>
<span style={{ color: '#fff', fontWeight: 800, fontSize: '30px', letterSpacing: '-0.01em' }}>BreakPilot</span>
<span style={{ color: 'rgba(255,255,255,0.55)', fontWeight: 400, fontSize: '15px' }}>
{de ? 'Compliance & Code-Security' : 'Compliance & Code Security'}
</span>
</div>
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center', padding: '28px 32px' }}>
<p style={{ fontSize: '12px', color: COLORS.indigo, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '10px', margin: '0 0 10px' }}>
{instrument} · {versionName}
</p>
<h1 style={{ fontSize: '40px', fontWeight: 800, color: COLORS.dark, lineHeight: 1.1, margin: '0 0 12px' }}>
{company?.name || 'BreakPilot'}
</h1>
<p style={{ fontSize: '16px', color: COLORS.med, maxWidth: '400px', lineHeight: 1.55, margin: '0 0 32px' }}>
{de ? (company?.tagline_de || 'Kontinuierliche Compliance für europäische Unternehmen.') : (company?.tagline_en || 'Continuous compliance for European companies.')}
</p>
<div style={{ display: 'flex', gap: '40px' }}>
{([
[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]) => (
<div key={label}>
<p style={{ fontSize: '9px', color: COLORS.light, textTransform: 'uppercase', letterSpacing: '0.06em', margin: '0 0 3px' }}>{label}</p>
<p style={{ fontSize: '14px', fontWeight: 700, color: COLORS.dark, margin: 0 }}>{val}</p>
</div>
))}
</div>
</div>
<div style={{ height: '40px', backgroundColor: '#f5f3ff', borderTop: `1px solid ${COLORS.border}`, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<span style={{ fontSize: '10px', color: COLORS.indigo, fontWeight: 600, letterSpacing: '0.08em', textTransform: 'uppercase' }}>
{de ? 'Vertraulich — Nur für Investoren' : 'Confidential — For Investor Use Only'}
</span>
</div>
</div>
</div>
)
}
export function PrintProblemPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
const de = lang === 'de'
const cards = de ? DE_PROBLEM_CARDS : EN_PROBLEM_CARDS
return (
<PrintPage title={de ? 'Das Problem' : 'The Problem'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<SectionTitle subtitle={de ? 'Europäische Unternehmen im Dilemma' : 'European companies in a dilemma'}>
{de ? 'Das Problem' : 'The Problem'}
</SectionTitle>
<div style={{ display: 'flex', gap: '12px', flex: 1 }}>
{cards.map((c) => (
<div key={c.title} style={{ flex: 1, border: `1px solid ${COLORS.border}`, borderRadius: '10px', padding: '16px', borderTop: `3px solid ${COLORS.indigo}`, display: 'flex', flexDirection: 'column' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '12px', flexShrink: 0 }}>
<span style={{ fontSize: '9px', fontWeight: 700, color: '#fff', background: COLORS.indigo, padding: '3px 9px', borderRadius: '99px', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>{c.stat}</span>
<span style={{ fontSize: '13px', fontWeight: 700, color: COLORS.dark }}>{c.title}</span>
</div>
<p style={{ fontSize: '11px', color: COLORS.med, lineHeight: 1.6, margin: 0, flex: 1 }}>{c.desc}</p>
</div>
))}
</div>
<div style={{ marginTop: '14px', padding: '12px 16px', background: '#f5f3ff', borderRadius: '8px', borderLeft: `3px solid ${COLORS.indigo}`, flexShrink: 0, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<p style={{ fontSize: '11px', fontStyle: 'italic', color: COLORS.med, margin: 0, lineHeight: 1.55 }}>
{de ? '„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."' : '"Manufacturing companies need an AI solution that runs in Europe, protects their code and automates compliance — without giving their data to US corporations."'}
</p>
</div>
</PrintPage>
)
}
export function PrintSolutionPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
const de = lang === 'de'
const pillars = de ? DE_PILLARS : EN_PILLARS
const icons = ['⬡', '◎', '▦']
return (
<PrintPage title={de ? 'Die Lösung' : 'The Solution'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<SectionTitle subtitle={de ? 'Kontinuierliche Software-Compliance statt jährlicher Stichproben' : 'Continuous software compliance instead of annual spot checks'}>
{de ? 'Die Lösung' : 'The Solution'}
</SectionTitle>
<div style={{ display: 'flex', gap: '12px', flex: 1 }}>
{pillars.map((p, i) => (
<div key={p.title} style={{ flex: 1, border: `1px solid ${COLORS.border}`, borderRadius: '10px', padding: '16px', display: 'flex', flexDirection: 'column' }}>
<div style={{ width: '36px', height: '36px', borderRadius: '8px', background: COLORS.indigoLight, display: 'flex', alignItems: 'center', justifyContent: 'center', marginBottom: '12px', fontSize: '18px', color: COLORS.indigo, flexShrink: 0, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
{icons[i]}
</div>
<h3 style={{ fontSize: '13px', fontWeight: 700, color: COLORS.dark, marginBottom: '10px', marginTop: 0, flexShrink: 0 }}>{p.title}</h3>
<p style={{ fontSize: '11px', color: COLORS.med, lineHeight: 1.6, margin: 0, flex: 1 }}>{p.desc}</p>
</div>
))}
</div>
<div style={{ marginTop: '14px', display: 'flex', gap: '8px', flexShrink: 0 }}>
{(de
? ['BSI-Cloud DE', 'DSGVO-konform', 'Kein US-SaaS', 'Kontinuierlich, nicht einmal/Jahr']
: ['BSI Cloud DE', 'GDPR Compliant', 'No US SaaS', 'Continuous, not once/year']
).map(tag => <Badge key={tag}>{tag}</Badge>)}
</div>
</PrintPage>
)
}
export function PrintProductPage({ products, lang, pageNum, totalPages, versionName }: SlideBase & { products: PitchProduct[] }) {
const de = lang === 'de'
const headers = [de ? 'Produkt' : 'Product', de ? 'Preis/Monat' : 'Price/Month', 'Hardware', de ? 'Key Features' : 'Key Features']
const rows = products.map(p => [
<span key="n" style={{ fontWeight: 600 }}>{p.name}{p.is_popular ? <span style={{ marginLeft: 6, fontSize: '8px', color: COLORS.indigo, fontWeight: 700 }}> Beliebt</span> : null}</span>,
fmtEur(p.monthly_price_eur),
p.hardware || '—',
(de ? p.features_de : p.features_en)?.slice(0, 3).join(', ') || '—',
])
return (
<PrintPage title={de ? 'Modularer Baukasten' : 'Modular Toolkit'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<SectionTitle subtitle={de ? 'Kunden wählen die Module, die sie brauchen' : 'Customers choose the modules they need'}>
{de ? 'Produkte & Pricing' : 'Products & Pricing'}
</SectionTitle>
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'space-between' }}>
<PrintTable headers={headers} rows={rows} colWidths={['22%', '16%', '20%', '42%']} />
<div style={{ padding: '14px 16px', background: '#f0fdf4', borderRadius: '8px', border: '1px solid #bbf7d0', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<p style={{ fontSize: '12px', color: '#166534', margin: 0, fontWeight: 500 }}>
{de
? '💡 Kunden zahlen ~50.000 EUR/Jahr und sparen >50.000 EUR (Pentests + CE-Beurteilungen + Auditmanager). ROI ab Tag 1.'
: '💡 Customers pay ~EUR 50,000/year and save >EUR 50,000 (pentests + CE assessments + audit managers). ROI from day 1.'}
</p>
</div>
</div>
</PrintPage>
)
}
export function PrintMarketPage({ market, lang, pageNum, totalPages, versionName }: SlideBase & { market: PitchMarket[] }) {
const de = lang === 'de'
const headers = [de ? 'Segment' : 'Segment', de ? 'Marktbeschreibung' : 'Description', de ? 'Volumen' : 'Volume', de ? 'Wachstum p.a.' : 'Growth p.a.', de ? 'Quelle' : 'Source']
const rows = market.map(m => [
<span key="s" style={{ fontWeight: 700, color: COLORS.indigo, fontSize: '11px' }}>{m.market_segment.toUpperCase()}</span>,
m.label,
fmtEur(m.value_eur),
`${m.growth_rate_pct}%`,
m.source,
])
return (
<PrintPage title={de ? 'Marktchance' : 'Market Opportunity'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<SectionTitle subtitle={de ? 'Compliance & Code-Security für produzierende Unternehmen' : 'Compliance & Code Security for manufacturing companies'}>
{de ? 'Marktchance' : 'Market Opportunity'}
</SectionTitle>
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'space-between' }}>
<PrintTable headers={headers} rows={rows} colWidths={['10%', '35%', '18%', '14%', '23%']} />
<div style={{ display: 'flex', gap: '12px' }}>
{market.map(m => (
<div key={m.market_segment} style={{ flex: 1, padding: '14px', background: COLORS.indigoLight, borderRadius: '8px', textAlign: 'center', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<p style={{ fontSize: '10px', color: COLORS.light, margin: '0 0 4px', textTransform: 'uppercase', letterSpacing: '0.06em' }}>{m.market_segment}</p>
<p style={{ fontSize: '20px', fontWeight: 800, color: COLORS.indigo, margin: '0 0 2px' }}>{fmtEur(m.value_eur)}</p>
<p style={{ fontSize: '10px', color: COLORS.med, margin: 0 }}>{m.growth_rate_pct}% p.a.</p>
</div>
))}
</div>
</div>
</PrintPage>
)
}
export function PrintTeamPage({ team, lang, pageNum, totalPages, versionName }: SlideBase & { team: PitchTeamMember[] }) {
const de = lang === 'de'
return (
<PrintPage title={de ? 'Das Team' : 'The Team'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<SectionTitle subtitle={de ? 'Gründer mit Domain-Expertise' : 'Founders with domain expertise'}>
{de ? 'Das Team' : 'The Team'}
</SectionTitle>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '12px', flex: 1 }}>
{team.slice(0, 4).map(m => (
<div key={m.id} style={{ border: `1px solid ${COLORS.border}`, borderRadius: '10px', padding: '16px', borderLeft: `3px solid ${COLORS.indigo}`, display: 'flex', flexDirection: 'column' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '10px', flexShrink: 0 }}>
<div>
<p style={{ fontSize: '15px', fontWeight: 700, color: COLORS.dark, margin: 0 }}>{m.name}</p>
<p style={{ fontSize: '11px', color: COLORS.indigo, margin: '3px 0 0', fontWeight: 600 }}>{de ? m.role_de : m.role_en}</p>
</div>
{m.equity_pct > 0 && <Badge>{m.equity_pct}%</Badge>}
</div>
<p style={{ fontSize: '11px', color: COLORS.med, lineHeight: 1.6, margin: 0, flex: 1, overflow: 'hidden' }}>
{de ? m.bio_de : m.bio_en}
</p>
{m.expertise?.length > 0 && (
<div style={{ marginTop: '10px', display: 'flex', flexWrap: 'wrap', gap: '4px', flexShrink: 0 }}>
{m.expertise.slice(0, 5).map(e => <Badge key={e} color="#6b7280">{e}</Badge>)}
</div>
)}
</div>
))}
</div>
</PrintPage>
)
}
export function PrintMilestonesPage({ milestones, lang, pageNum, totalPages, versionName }: SlideBase & { milestones: PitchMilestone[] }) {
const de = lang === 'de'
const ordered = [...milestones].sort((a, b) => {
const order = { completed: 0, in_progress: 1, planned: 2 }
return (order[a.status] - order[b.status]) || new Date(a.milestone_date).getTime() - new Date(b.milestone_date).getTime()
}).slice(0, 10)
const statusColor = (s: string) => ({ completed: '#16a34a', in_progress: '#d97706', planned: '#94a3b8' }[s] || '#94a3b8')
const statusLabel = (s: string) => de
? ({ completed: 'Abgeschlossen', in_progress: 'In Arbeit', planned: 'Geplant' }[s] || s)
: ({ completed: 'Completed', in_progress: 'In Progress', planned: 'Planned' }[s] || s)
const fmtDate = (d: string) => new Date(d).toLocaleDateString(de ? 'de-DE' : 'en-GB', { month: 'short', year: 'numeric' })
const rows = ordered.map(m => [
fmtDate(m.milestone_date),
<span key="t" style={{ fontWeight: 500 }}>{de ? m.title_de : m.title_en}</span>,
m.category,
<span key="s" style={{ color: statusColor(m.status), fontWeight: 600 }}>{statusLabel(m.status)}</span>,
])
return (
<PrintPage title={de ? 'Meilensteine' : 'Milestones'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<SectionTitle subtitle={de ? 'Was wir bereits erreicht haben — und was als Nächstes kommt' : 'What we have achieved — and what comes next'}>
{de ? 'Meilensteine' : 'Milestones'}
</SectionTitle>
<div style={{ flex: 1 }}>
<PrintTable headers={[de ? 'Datum' : 'Date', de ? 'Meilenstein' : 'Milestone', de ? 'Kategorie' : 'Category', de ? 'Status' : 'Status']} rows={rows} colWidths={['13%', '47%', '22%', '18%']} />
</div>
</PrintPage>
)
}
export function PrintTheAskPage({ funding, lang, pageNum, totalPages, versionName }: SlideBase & { funding: PitchFunding }) {
const de = lang === 'de'
const amount = Number(funding?.amount_eur) || 0
const isWD = (funding?.instrument || '').toLowerCase() === 'wandeldarlehen'
const targetDate = funding?.target_date ? (() => { const d = new Date(funding.target_date); return `Q${Math.ceil((d.getMonth()+1)/3)} ${d.getFullYear()}` })() : 'TBD'
const uof = funding?.use_of_funds || []
const amountLabel = amount >= 1_000_000 ? `${(amount / 1_000_000).toFixed(1)} Mio. EUR` : amount >= 1_000 ? `${Math.round(amount / 1_000)}k EUR` : `${amount} EUR`
return (
<PrintPage title="The Ask" pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<SectionTitle subtitle={de ? 'Pre-Seed Finanzierung' : 'Pre-Seed Funding'}>The Ask</SectionTitle>
<div style={{ display: 'flex', gap: '32px', flex: 1 }}>
<div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', flexShrink: 0, minWidth: '200px' }}>
<p style={{ fontSize: '48px', fontWeight: 800, color: COLORS.indigo, margin: 0, lineHeight: 1 }}>{amountLabel}</p>
<div style={{ marginTop: '20px', display: 'flex', flexDirection: 'column', gap: '8px' }}>
{([
[de ? 'Instrument' : 'Instrument', isWD ? 'Wandeldarlehen' : 'Equity'],
[de ? 'Runde' : 'Round', funding?.round_name || 'Pre-Seed'],
[de ? 'Zieldatum' : 'Target', targetDate],
] as [string, string][]).map(([k, v]) => (
<div key={k} style={{ display: 'flex', gap: '10px', alignItems: 'center' }}>
<span style={{ fontSize: '11px', color: COLORS.light, minWidth: '80px' }}>{k}</span>
<span style={{ fontSize: '13px', fontWeight: 600, color: COLORS.dark }}>{v}</span>
</div>
))}
</div>
</div>
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
<p style={{ fontSize: '11px', fontWeight: 700, color: COLORS.dark, textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: '14px', marginTop: 0 }}>
{de ? 'Mittelverwendung' : 'Use of Funds'}
</p>
{uof.map(u => (
<div key={u.category} style={{ marginBottom: '10px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '4px' }}>
<span style={{ fontSize: '12px', color: COLORS.med }}>{de ? u.label_de : u.label_en}</span>
<span style={{ fontSize: '13px', fontWeight: 700, color: COLORS.indigo }}>{u.percentage}%</span>
</div>
<div style={{ height: '8px', background: '#e0e7ff', borderRadius: '4px', overflow: 'hidden', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<div style={{ height: '100%', width: `${u.percentage}%`, background: COLORS.indigo, borderRadius: '4px', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
</div>
</div>
))}
</div>
</div>
</PrintPage>
)
}
@@ -3,23 +3,35 @@
import { useEffect } from 'react'
import { Language, PitchData, FMResult, FMAssumption } from '@/lib/types'
import {
PrintCoverPage, PrintProblemPage, PrintSolutionPage, PrintProductPage,
PrintMarketPage, PrintTeamPage, PrintMilestonesPage, PrintTheAskPage,
} from './PrintCoreSlides'
PrintCoverPage, PrintExecSummaryPage1, PrintExecSummaryPage2,
PrintProblemPage, PrintSolutionPage,
} from './PrintIntroSlides'
import {
PrintFinancialsPage, PrintAssumptionsPage, PrintCapTablePage,
PrintDisclaimerPage, aggregateAnnualRows,
} from './PrintFinancialSlides'
PrintUSPPage1, PrintUSPPage2, PrintRegulatoryLandscapePage,
PrintProductPage, PrintHowItWorksPage, PrintBusinessModelPage,
} from './PrintProductSlides'
import {
PrintExecutiveSummaryPage, PrintUSPPage, PrintRegulatoryLandscapePage,
PrintHowItWorksPage, PrintBusinessModelPage, PrintCompetitionPage,
PrintCustomerSavingsPage,
} from './PrintExtraSlides'
PrintMarketPage, PrintMilestonesPage, PrintTeamPage,
PrintTheAskPage, PrintCustomerSavingsPage,
} from './PrintMarketSlides'
import {
PrintStrategyPage, PrintFinanzplanPage, PrintRegulatoryPage,
PrintArchitecturePage, PrintEngineeringPage, PrintAIPipelinePage,
PrintRisksPage, PrintGlossaryPage,
PrintCompetitionPage1, PrintCompetitionPage2,
} from './PrintCompetitionSlides'
import {
PrintStrategyPage, PrintRegulatoryPage, PrintArchitecturePage,
PrintEngineeringPage, PrintAIPipelinePage, PrintRisksPage, PrintGlossaryPage,
} from './PrintAnnexSlides'
import {
PrintTLDRPage, PrintDifferentiatorsPage, PrintKPIHeroPage,
PrintTechStackPage, PrintAnnexDividerPage,
} from './PrintNewSlides'
import {
PrintFinanzplanPage1, PrintFinanzplanPage2, PrintAssumptionsPage,
PrintFinancialsPage, PrintCapTablePage, PrintDisclaimerPage,
aggregateAnnualRows,
} from './PrintFinancialSlides'
export { aggregateAnnualRows }
interface PrintDeckProps {
pitchData: PitchData
@@ -31,46 +43,55 @@ interface PrintDeckProps {
}
export default function PrintDeck({ pitchData, versionName, fmResults, fmAssumptions, financial, lang }: PrintDeckProps) {
const isWandeldarlehen = (pitchData.funding?.instrument || '').toLowerCase() === 'wandeldarlehen'
const isWandeldarlehen = (pitchData.funding?.instrument || '').toLowerCase().includes('wandeldarlehen') ||
(pitchData.funding?.instrument || '').toLowerCase().includes('convertible')
const hasCapTable = financial && !isWandeldarlehen
const annualRows = aggregateAnnualRows(fmResults)
const hasFinancials = financial && annualRows.length > 0
// Standard = 25 slides. Financial adds: detailed financials page + (optional) cap-table.
const totalPages = 25 + (hasFinancials ? 1 : 0) + (hasCapTable ? 1 : 0)
const hasFinancialData = annualRows.length > 0
const de = lang === 'de'
// Base standard PDF: 35 physical pages.
// 2 (exec) + 1 (TL;DR) + 1 (cover) + 1 (problem) + 1 (solution) + 2 (usp) +
// 1 (differentiators) + 1 (regL) + 1 (product) + 1 (how) + 1 (market) + 1 (bm) +
// 1 (milestones) + 2 (competition) + 1 (team) + 1 (ask) + 1 (savings) +
// 1 (anhang-divider) + 1 (strategy) + 1 (kpis) + 2 (finanzplan) + 1 (p&l detail) +
// 1 (assumptions) + 1 (regulatory) + 1 (architecture) + 1 (engineering) +
// 1 (tech-stack) + 1 (aipipeline) + 1 (risks) + 1 (glossary) + 1 (disclaimer) = 35
// P&L detail is now in standard PDF (was financial-only); cap-table stays
// financial-only (and is suppressed for Wandeldarlehen).
const BASE_PAGES = 35
const totalPages = BASE_PAGES + (hasCapTable ? 1 : 0)
void hasFinancialData
useEffect(() => {
const t = setTimeout(() => window.print(), 900)
return () => clearTimeout(t)
}, [])
let n = 0
function p() {
n += 1
return { lang, pageNum: n, totalPages, versionName }
}
const p = () => ({ lang, pageNum: ++n, totalPages, versionName })
return (
<>
{/* Toolbar screen only */}
{/* Toolbar, screen only */}
<div className="no-print" style={{
position: 'sticky', top: 0, zIndex: 100,
background: '#1e1b4b', color: '#fff',
background: '#0f172a', color: '#fff',
padding: '10px 24px',
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
fontFamily: 'system-ui, -apple-system, sans-serif',
fontFamily: "'Plus Jakarta Sans', system-ui, sans-serif",
fontSize: '13px', boxShadow: '0 2px 8px rgba(0,0,0,0.3)',
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
<span style={{ fontWeight: 700 }}>BreakPilot</span>
<span style={{ opacity: 0.6, fontSize: '11px' }}>{versionName}</span>
<span style={{ fontSize: '11px', padding: '2px 8px', borderRadius: '99px', background: financial ? '#7c3aed22' : '#6366f122', color: financial ? '#a78bfa' : '#818cf8' }}>
{financial ? (de ? 'PDF + Finanzen' : 'PDF + Financial') : 'Standard PDF'}
<span style={{ fontWeight: 800, letterSpacing: '-0.01em' }}>BreakPilot</span>
<span style={{ opacity: 0.55, fontSize: '11px' }}>{versionName}</span>
<span style={{ fontSize: '11px', padding: '2px 8px', borderRadius: '99px', background: financial ? '#4f46e533' : '#6366f133', color: financial ? '#a5b4fc' : '#a5b4fc', fontWeight: 600, letterSpacing: '0.02em' }}>
{financial ? (de ? 'PDF + Finanzen' : 'PDF + Financial') : (de ? 'Standard PDF' : 'Standard PDF')}
</span>
<span style={{ fontSize: '11px', opacity: 0.5 }}>{totalPages} {de ? 'Seiten' : 'pages'}</span>
<span style={{ fontSize: '11px', opacity: 0.5, fontVariantNumeric: 'tabular-nums' }}>{totalPages} {de ? 'Seiten' : 'pages'}</span>
</div>
<div style={{ display: 'flex', gap: '10px' }}>
<button onClick={() => window.print()} style={{ padding: '6px 16px', background: '#6366f1', color: '#fff', border: 'none', borderRadius: '6px', cursor: 'pointer', fontWeight: 600, fontSize: '13px' }}>
<button onClick={() => window.print()} style={{ padding: '6px 16px', background: '#4f46e5', color: '#fff', border: 'none', borderRadius: '6px', cursor: 'pointer', fontWeight: 600, fontSize: '13px' }}>
{de ? 'Drucken / Als PDF speichern' : 'Print / Save as PDF'}
</button>
<button onClick={() => window.close()} style={{ padding: '6px 12px', background: 'rgba(255,255,255,0.1)', color: '#fff', border: '1px solid rgba(255,255,255,0.2)', borderRadius: '6px', cursor: 'pointer', fontSize: '13px' }}>
@@ -79,62 +100,109 @@ export default function PrintDeck({ pitchData, versionName, fmResults, fmAssumpt
</div>
</div>
<div className="print-deck-wrapper" style={{ padding: '32px 0' }}>
{/* Slide order mirrors lib/slide-order.ts, minus intro-presenter, ai-qa, annex-sdk-demo. */}
<div className="print-deck-wrapper" style={{ padding: '32px 0', fontFamily: "'Inter', system-ui, sans-serif" }}>
{/* PITCH (slides 0117) */}
{/* 1. executive-summary */}
<PrintExecutiveSummaryPage market={pitchData.market || []} funding={pitchData.funding} {...p()} />
{/* 2. cover (page 2 — uses its own layout; assign sequential number) */}
{/* 0102 executive-summary (2 pages) */}
<PrintExecSummaryPage1 market={pitchData.market || []} {...p()} />
<PrintExecSummaryPage2 {...p()} />
{/* 03 TL;DR — 30 Sekunden */}
<PrintTLDRPage {...p()} />
{/* 04 cover */}
{(() => { n += 1; return <PrintCoverPage company={pitchData.company} funding={pitchData.funding} versionName={versionName} lang={lang} /> })()}
{/* 3. problem */}
{/* 05 problem */}
<PrintProblemPage {...p()} />
{/* 4. solution */}
{/* 06 solution */}
<PrintSolutionPage {...p()} />
{/* 5. usp */}
<PrintUSPPage {...p()} />
{/* 6. regulatory-landscape */}
{/* 0708 usp (2 pages) */}
<PrintUSPPage1 {...p()} />
<PrintUSPPage2 {...p()} />
{/* 09 differentiators */}
<PrintDifferentiatorsPage {...p()} />
{/* 10 regulatory-landscape */}
<PrintRegulatoryLandscapePage {...p()} />
{/* 7. product */}
{/* 11 product / modular-toolkit */}
<PrintProductPage products={pitchData.products || []} {...p()} />
{/* 8. how-it-works */}
{/* 12 how-it-works */}
<PrintHowItWorksPage {...p()} />
{/* 9. market */}
{/* 13 market */}
<PrintMarketPage market={pitchData.market || []} {...p()} />
{/* 10. business-model */}
{/* 14 business-model / pricing */}
<PrintBusinessModelPage {...p()} />
{/* 11. traction (uses milestones table) */}
{/* 15 traction (milestones) */}
<PrintMilestonesPage milestones={pitchData.milestones || []} {...p()} />
{/* 12. competition */}
<PrintCompetitionPage {...p()} />
{/* 13. team */}
{/* 1617 competition (2 pages) */}
<PrintCompetitionPage1 {...p()} />
<PrintCompetitionPage2 {...p()} />
{/* 18 team */}
<PrintTeamPage team={pitchData.team || []} {...p()} />
{/* 14. the-ask */}
{/* 19 the-ask */}
<PrintTheAskPage funding={pitchData.funding} {...p()} />
{/* 15. customer-savings */}
{/* 20 customer-savings */}
<PrintCustomerSavingsPage {...p()} />
{/* 16. annex-strategy */}
{/* 21 ANHANG divider — chapter break before the appendix */}
<PrintAnnexDividerPage {...p()} />
{/* APPENDIX (slides 2235) */}
{/* 22 annex-strategy */}
<PrintStrategyPage {...p()} />
{/* 17. annex-finanzplan */}
<PrintFinanzplanPage fmResults={fmResults} {...p()} />
{/* Financial-only: detailed P&L table */}
{hasFinancials && <PrintFinancialsPage annualRows={annualRows} {...p()} />}
{/* 18. annex-assumptions */}
{/* 23 KPIs — 2026 → 2030 trajectory */}
<PrintKPIHeroPage fmResults={fmResults} {...p()} />
{/* 2425 annex-finanzplan (2 pages) */}
<PrintFinanzplanPage1 fmResults={fmResults} {...p()} />
<PrintFinanzplanPage2 fmResults={fmResults} {...p()} />
{/* 26 P&L detail (was financial-only; now standard) */}
<PrintFinancialsPage annualRows={annualRows} {...p()} />
{/* 27 annex-assumptions */}
<PrintAssumptionsPage assumptions={fmAssumptions} {...p()} />
{/* 19. annex-regulatory */}
{/* 28 annex-regulatory */}
<PrintRegulatoryPage {...p()} />
{/* 20. annex-architecture */}
{/* 29 annex-architecture */}
<PrintArchitecturePage {...p()} />
{/* 21. annex-engineering */}
{/* 30 annex-engineering */}
<PrintEngineeringPage {...p()} />
{/* 22. annex-aipipeline */}
{/* 31 tech-stack */}
<PrintTechStackPage {...p()} />
{/* 32 annex-aipipeline */}
<PrintAIPipelinePage {...p()} />
{/* 23. risks */}
{/* 33 risks */}
<PrintRisksPage {...p()} />
{/* 24. annex-glossary */}
{/* 34 annex-glossary */}
<PrintGlossaryPage {...p()} />
{/* Financial-only: cap table */}
{/* Financial-only: cap-table (suppressed for Wandeldarlehen) */}
{hasCapTable && <PrintCapTablePage {...p()} />}
{/* 25. legal-disclaimer */}
{/* 35 legal-disclaimer */}
<PrintDisclaimerPage {...p()} />
</div>
</>
@@ -0,0 +1,298 @@
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 (
<div style={{
border: `1px solid ${COLORS.slate200}`,
borderTop: `3px solid ${accent}`,
background: '#ffffff',
padding: '3mm 4mm',
display: 'flex',
flexDirection: 'column',
WebkitPrintColorAdjust: 'exact',
printColorAdjust: 'exact',
}}>
{kicker && (
<div style={{ fontSize: '6.5pt', fontWeight: 700, color: accent, textTransform: 'uppercase', letterSpacing: '0.14em', marginBottom: '1.5mm' }}>{kicker}</div>
)}
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '3mm', marginBottom: '1mm' }}>
{icon && <div style={{ flexShrink: 0, color: accent, marginTop: '0.5mm' }}>{icon}</div>}
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: '11pt', fontWeight: 700, color: COLORS.slate900, lineHeight: 1.15 }}>{title}</div>
{subtitle && <div style={{ fontSize: '8pt', color: accent, fontWeight: 600, marginTop: '0.5mm' }}>{subtitle}</div>}
</div>
</div>
{items && items.length > 0 && (
<div style={{ marginTop: '2mm', display: 'flex', flexDirection: 'column' }}>
{items.map((it, i) => (
<div key={i} style={{
fontSize: '7.5pt',
color: COLORS.slate700,
padding: '1mm 0',
borderTop: i > 0 ? `1px solid ${COLORS.slate100}` : 'none',
lineHeight: 1.35,
}}>{it}</div>
))}
</div>
)}
{footer && (
<div style={{ marginTop: '2mm', paddingTop: '1.5mm', borderTop: `1px solid ${COLORS.slate100}`, fontSize: '6.5pt', color: COLORS.slate500, fontFamily: 'monospace', lineHeight: 1.4 }}>{footer}</div>
)}
</div>
)
}
/** Vertical down arrow (between rows of a flow diagram). */
export function VArrow({ color = COLORS.slate400, label }: { color?: string; label?: string }) {
return (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: '1.5mm 0' }}>
<svg viewBox="0 0 24 32" style={{ width: '6mm', height: '6mm' }}>
<line x1="12" y1="0" x2="12" y2="24" stroke={color} strokeWidth="1.5" />
<polyline points="6,22 12,30 18,22" fill="none" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
{label && <div style={{ fontSize: '6.5pt', color: COLORS.slate500, marginTop: '0.5mm', fontWeight: 600 }}>{label}</div>}
</div>
)
}
/** Horizontal right arrow (between steps of a horizontal flow). */
export function HArrow({ color = COLORS.slate400, label }: { color?: string; label?: string }) {
return (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', minWidth: '6mm', position: 'relative' }}>
<svg viewBox="0 0 32 24" style={{ width: '8mm', height: '5mm' }}>
<line x1="0" y1="12" x2="24" y2="12" stroke={color} strokeWidth="1.5" />
<polyline points="20,6 30,12 20,18" fill="none" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
{label && <div style={{ fontSize: '6.5pt', color: COLORS.slate500, marginTop: '0.5mm', fontWeight: 600, textAlign: 'center', whiteSpace: 'nowrap' }}>{label}</div>}
</div>
)
}
/** 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 (
<div style={{ display: 'flex', alignItems: 'stretch' }}>
{steps.map((s, i) => (
<React.Fragment key={i}>
<div style={{ flex: 1, padding: '0 2mm', display: 'flex', flexDirection: 'column' }}>
<div style={{ display: 'flex', alignItems: 'baseline', gap: '3mm', marginBottom: '2mm' }}>
<span style={{ fontSize: '24pt', fontWeight: 800, color: accent, lineHeight: 1, fontVariantNumeric: 'tabular-nums', letterSpacing: '-0.02em' }}>{s.n}</span>
<div style={{ flex: 1, height: '1px', background: COLORS.slate200, alignSelf: 'center' }} />
</div>
<div style={{ fontSize: '12pt', fontWeight: 700, color: COLORS.slate900, lineHeight: 1.2, marginBottom: '2mm', letterSpacing: '-0.005em' }}>{s.t}</div>
<div style={{ fontSize: '8.5pt', color: COLORS.slate700, lineHeight: 1.5, flex: 1 }}>{s.d}</div>
</div>
{i < steps.length - 1 && (
<div style={{ width: '6mm', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<svg viewBox="0 0 24 24" style={{ width: '6mm', height: '6mm', color: COLORS.slate300 }}>
<line x1="2" y1="12" x2="18" y2="12" stroke={COLORS.slate300} strokeWidth="1.5" />
<polyline points="14,6 20,12 14,18" fill="none" stroke={COLORS.slate300} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
)}
</React.Fragment>
))}
</div>
)
}
/** 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'
const MONO = "'JetBrains Mono', ui-monospace, monospace"
/** Layer pill: "01 · APPLICATION LAYER" + sub-label */
const LayerChip = ({ n, label, sub, tint }: { n: string; label: string; sub: string; tint: string }) => (
<div style={{ display: 'flex', alignItems: 'center', gap: '3mm', marginBottom: '2mm' }}>
<span style={{ fontFamily: MONO, fontSize: '7pt', fontWeight: 700, color: tint, textTransform: 'uppercase', letterSpacing: '0.2em', background: '#ffffff', padding: '1mm 3mm', borderRadius: '99pt', border: `1px solid ${tint}66`, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>{n} &middot; {label}</span>
<span style={{ fontSize: '7.5pt', color: COLORS.slate600, fontStyle: 'italic' }}>{sub}</span>
</div>
)
/**
* ServiceNode — flat, no own border. Used INSIDE a tinted layer card so the
* whole layer reads as one panel rather than nested boxes (which is what
* was causing the architecture diagram to overflow).
*/
const ServiceNode = ({ kicker, title, subtitle, items, footer, accent }: { kicker?: string; title: string; subtitle?: string; items: string[]; footer?: string; accent: string }) => (
<div style={{ padding: '0 1mm', display: 'flex', flexDirection: 'column' }}>
{kicker && <div style={{ fontFamily: MONO, fontSize: '6pt', fontWeight: 700, color: accent, textTransform: 'uppercase', letterSpacing: '0.16em', marginBottom: '0.5mm' }}>{kicker}</div>}
<div style={{ fontSize: '10pt', fontWeight: 700, color: COLORS.slate900, lineHeight: 1.15 }}>{title}</div>
{subtitle && <div style={{ fontSize: '7.5pt', color: accent, fontWeight: 600, marginTop: '0.5mm' }}>{subtitle}</div>}
<div style={{ marginTop: '1.5mm' }}>
{items.slice(0, 4).map((it, i) => (
<div key={i} style={{ fontSize: '7pt', color: COLORS.slate700, lineHeight: 1.35, padding: '0.4mm 0', borderTop: i > 0 ? `1px solid ${COLORS.slate100}` : 'none' }}>&middot; {it}</div>
))}
</div>
{footer && <div style={{ marginTop: '1.5mm', paddingTop: '1mm', borderTop: `1px solid ${COLORS.slate200}`, fontFamily: MONO, fontSize: '6pt', color: COLORS.slate500, lineHeight: 1.4 }}>{footer}</div>}
</div>
)
const productLayerBg = `linear-gradient(135deg, ${COLORS.violet50} 0%, #ffffff 60%, ${COLORS.violet50} 100%)`
const proxyLayerBg = `linear-gradient(135deg, ${COLORS.amber50} 0%, #fffaf0 60%, ${COLORS.amber50} 100%)`
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '2mm' }}>
{/* APPLICATION (PRODUCT) LAYER */}
<div style={{ background: productLayerBg, border: `1px solid ${COLORS.violet200}`, borderRadius: '4pt', padding: '3mm 4mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<LayerChip n="01" label={de ? 'Application Layer' : 'Application Layer'} sub={de ? 'Kundenseitige Services' : 'User-facing services'} tint={COLORS.violet600} />
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '5mm' }}>
{product.map((p, i) => (
<ServiceNode key={i} kicker={p.kicker} title={p.title} subtitle={p.subtitle} items={p.services} accent={COLORS.violet600} footer={p.tech} />
))}
</div>
</div>
{/* compact connector strip */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '4mm', padding: '0 5mm', height: '5mm', alignItems: 'center' }}>
{[0, 1, 2].map(i => (
<div key={i} style={{ display: 'flex', justifyContent: 'center' }}>
<svg viewBox="0 0 12 16" width="3mm" height="5mm">
<line x1="6" y1="0" x2="6" y2="12" stroke={COLORS.violet500} strokeWidth="1.2" />
<polyline points="3,11 6,15 9,11" fill="none" stroke={COLORS.violet500} strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
))}
</div>
{/* GATEWAY LAYER — compact: title row + features in 1 row */}
<div style={{ background: proxyLayerBg, border: `1.5px solid ${COLORS.amber600}`, borderRadius: '4pt', padding: '3mm 4mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<LayerChip n="02" label={de ? 'Gateway Layer' : 'Gateway Layer'} sub={de ? 'Routing & Guardrails' : 'Routing & guardrails'} tint={COLORS.amber700} />
<div style={{ display: 'flex', alignItems: 'baseline', gap: '4mm', marginBottom: '2mm' }}>
<span style={{ fontSize: '12pt', fontWeight: 800, color: COLORS.slate900, letterSpacing: '-0.005em' }}>{proxy.title}</span>
<span style={{ fontFamily: MONO, fontSize: '7pt', color: COLORS.amber700, fontWeight: 600 }}>{proxy.subtitle}</span>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: '3mm', fontSize: '7pt', color: COLORS.slate700 }}>
{proxy.features.map((f, i) => (
<div key={i} style={{ paddingLeft: '2mm', borderLeft: `1px solid ${COLORS.amber600}`, lineHeight: 1.3 }}>{f}</div>
))}
</div>
</div>
{/* compact connector strip */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '4mm', padding: '0 5mm', height: '5mm', alignItems: 'center' }}>
{[0, 1, 2].map(i => (
<div key={i} style={{ display: 'flex', justifyContent: 'center' }}>
<svg viewBox="0 0 12 16" width="3mm" height="5mm">
<line x1="6" y1="0" x2="6" y2="12" stroke={COLORS.amber600} strokeWidth="1.2" />
<polyline points="3,11 6,15 9,11" fill="none" stroke={COLORS.amber600} strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
))}
</div>
{/* INFRASTRUCTURE (INFERENCE) LAYER */}
<div style={{ background: productLayerBg, border: `1px solid ${COLORS.violet200}`, borderRadius: '4pt', padding: '3mm 4mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<LayerChip n="03" label={de ? 'Infrastructure Layer' : 'Infrastructure Layer'} sub={de ? 'Compute & Daten · lokal · air-gap-fähig' : 'Compute & data · local · air-gap capable'} tint={COLORS.violet600} />
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '5mm' }}>
{inference.map((p, i) => (
<ServiceNode key={i} title={p.title} subtitle={p.subtitle} items={[p.desc]} accent={COLORS.violet500} footer={p.tech} />
))}
</div>
</div>
</div>
)
}
/** 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 (
<svg viewBox="0 0 320 80" style={{ width: '100%', height: '24mm', display: 'block' }}>
<defs>
<marker id="arrR" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="5" markerHeight="5" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 z" fill={COLORS.indigo600} />
</marker>
</defs>
{/* Compliance box */}
<rect x="6" y="20" width="80" height="40" rx="2" fill={COLORS.indigo50} stroke={COLORS.indigo600} strokeWidth="1" />
<text x="46" y="38" fontSize="9" fontWeight="700" textAnchor="middle" fill={COLORS.indigo700}>Compliance</text>
<text x="46" y="52" fontSize="7" textAnchor="middle" fill={COLORS.slate600}>{de ? 'Policies · Audits · VVT' : 'Policies · Audits · RoPA'}</text>
{/* Code box */}
<rect x="234" y="20" width="80" height="40" rx="2" fill={COLORS.indigo50} stroke={COLORS.indigo600} strokeWidth="1" />
<text x="274" y="38" fontSize="9" fontWeight="700" textAnchor="middle" fill={COLORS.indigo700}>Code</text>
<text x="274" y="52" fontSize="7" textAnchor="middle" fill={COLORS.slate600}>{de ? 'Repos · CI/CD · Findings' : 'Repos · CI/CD · Findings'}</text>
{/* Top arrow: compliance → code */}
<path d="M86,30 Q160,5 234,30" fill="none" stroke={COLORS.indigo600} strokeWidth="1.4" markerEnd="url(#arrR)" />
<text x="160" y="12" fontSize="7" fontWeight="600" textAnchor="middle" fill={COLORS.indigo700}>{de ? 'Policies → Code (Real-time)' : 'Policies → Code (Real-time)'}</text>
{/* Bottom arrow: code → compliance */}
<path d="M234,50 Q160,75 86,50" fill="none" stroke={COLORS.indigo600} strokeWidth="1.4" markerEnd="url(#arrR)" />
<text x="160" y="72" fontSize="7" fontWeight="600" textAnchor="middle" fill={COLORS.indigo700}>{de ? 'Code-Δ → Evidence (Auto)' : 'Code-Δ → Evidence (Auto)'}</text>
</svg>
)
}
/** 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 (
<div style={{ display: 'flex', alignItems: 'stretch' }}>
{stages.map((s, i) => (
<React.Fragment key={i}>
<div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
<div style={{
border: `1px solid ${COLORS.slate200}`,
borderTop: `3px solid ${accent}`,
padding: '3mm 4mm',
display: 'flex',
flexDirection: 'column',
height: '100%',
WebkitPrintColorAdjust: 'exact',
printColorAdjust: 'exact',
}}>
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: '2mm' }}>
<span style={{ fontSize: '18pt', fontWeight: 800, color: accent, lineHeight: 1, fontVariantNumeric: 'tabular-nums' }}>{s.n}</span>
{s.kpi && <span style={{ fontSize: '7pt', color: COLORS.emerald700, fontWeight: 700, fontVariantNumeric: 'tabular-nums' }}>{s.kpi}</span>}
</div>
<div style={{ fontSize: '11pt', fontWeight: 700, color: COLORS.slate900, lineHeight: 1.2, marginBottom: '2mm' }}>{s.t}</div>
<div style={{ fontSize: '8pt', color: COLORS.slate700, lineHeight: 1.5, flex: 1 }}>{s.d}</div>
</div>
</div>
{i < stages.length - 1 && (
<div style={{ width: '7mm', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<svg viewBox="0 0 24 24" style={{ width: '6mm', height: '6mm' }}>
<line x1="2" y1="12" x2="18" y2="12" stroke={accent} strokeWidth="1.5" />
<polyline points="14,6 20,12 14,18" fill="none" stroke={accent} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
)}
</React.Fragment>
))}
</div>
)
}
@@ -1,480 +0,0 @@
import { PrintPage, SectionTitle, PrintTable, Badge, COLORS } from './PrintLayout'
import { Language, PitchMarket, PitchFunding } from '@/lib/types'
interface SlideBase { lang: Language; pageNum: number; totalPages: number; versionName: string }
function fmtEur(n: number) {
const abs = Math.abs(n)
if (abs >= 1_000_000_000) return `${(n / 1_000_000_000).toLocaleString('de-DE', { maximumFractionDigits: 1 })}B EUR`
if (abs >= 1_000_000) return `${(n / 1_000_000).toLocaleString('de-DE', { maximumFractionDigits: 1 })}M EUR`
if (abs >= 1_000) return `${(n / 1_000).toLocaleString('de-DE', { maximumFractionDigits: 0 })}k EUR`
return `${n.toLocaleString('de-DE')} EUR`
}
export function PrintExecutiveSummaryPage({ market, funding, lang, pageNum, totalPages, versionName }: SlideBase & { market: PitchMarket[]; funding: PitchFunding }) {
const de = lang === 'de'
const tam = market.find(m => m.market_segment === 'TAM')
const sam = market.find(m => m.market_segment === 'SAM')
const som = market.find(m => m.market_segment === 'SOM')
const moat = [
{ k: 'Traceability', v: de ? 'Gesetz → Control → Code' : 'Law → Control → Code' },
{ k: 'Continuous Engine', v: de ? 'Echtzeit bei jeder Änderung' : 'Real-time on every change' },
{ k: 'Compliance Optimizer', v: de ? 'Maximale KI-Nutzung im Rahmen' : 'Max AI use within regulations' },
{ k: 'EU-Trust Stack', v: de ? '100% EU, kein US-SaaS' : '100% EU, no US SaaS' },
]
const kpis = [
{ v: '25k+', l: de ? 'Controls' : 'Controls' },
{ v: '380+', l: de ? 'Regularien' : 'Regulations' },
{ v: '10', l: de ? 'Branchen' : 'Industries' },
{ v: '500K+', l: de ? 'Zeilen Code' : 'Lines of code' },
{ v: '80%', l: de ? 'Zeitersparnis' : 'Time saved' },
{ v: '10x', l: de ? 'Günstiger als Pentests' : 'Cheaper than pentests' },
]
return (
<PrintPage title={de ? 'Executive Summary' : 'Executive Summary'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<SectionTitle subtitle={de ? 'BreakPilot COMPLAI — DSGVO-konforme KI-Plattform für Code-Security und Compliance' : 'BreakPilot COMPLAI — GDPR-compliant AI platform for code security and compliance'}>
Executive Summary
</SectionTitle>
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: '10px' }}>
<div style={{ background: '#f5f3ff', borderLeft: `3px solid ${COLORS.indigo}`, padding: '10px 14px', borderRadius: '6px', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<p style={{ fontSize: '10px', color: COLORS.med, margin: 0, lineHeight: 1.55 }}>
{de
? 'Kontinuierliches Sicherheitsscanning + intelligente Compliance-Automatisierung. Code absichern, Compliance skalierbar durchsetzen, volle Datensouveränität — gestützt auf 25.000+ atomare Prüfaspekte.'
: 'Continuous security scanning + intelligent compliance automation. Secure code, enforce compliance at scale, maintain data sovereignty — powered by 25,000+ atomic audit aspects.'}
</p>
</div>
<div>
<p style={{ fontSize: '9px', fontWeight: 700, color: COLORS.indigo, textTransform: 'uppercase', letterSpacing: '0.06em', margin: '0 0 6px' }}>{de ? 'Unser MOAT' : 'Our MOAT'}</p>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '8px' }}>
{moat.map(m => (
<div key={m.k} style={{ border: `1px solid ${COLORS.border}`, borderRadius: '6px', padding: '8px 10px', borderTop: `2px solid ${COLORS.indigo}` }}>
<p style={{ fontSize: '10px', fontWeight: 700, color: COLORS.dark, margin: 0 }}>{m.k}</p>
<p style={{ fontSize: '9px', color: COLORS.light, margin: '2px 0 0' }}>{m.v}</p>
</div>
))}
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(6, 1fr)', gap: '6px' }}>
{kpis.map(k => (
<div key={k.l} style={{ background: COLORS.indigoLight, padding: '8px', borderRadius: '6px', textAlign: 'center', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<p style={{ fontSize: '15px', fontWeight: 800, color: COLORS.indigo, margin: 0, lineHeight: 1 }}>{k.v}</p>
<p style={{ fontSize: '8px', color: COLORS.med, margin: '3px 0 0', textTransform: 'uppercase', letterSpacing: '0.04em' }}>{k.l}</p>
</div>
))}
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px' }}>
<div style={{ border: `1px solid ${COLORS.border}`, borderRadius: '6px', padding: '10px', borderTop: `2px solid #ef4444` }}>
<p style={{ fontSize: '10px', fontWeight: 700, color: '#dc2626', margin: '0 0 6px', textTransform: 'uppercase', letterSpacing: '0.04em' }}>{de ? 'Problem' : 'Problem'}</p>
<ul style={{ margin: 0, paddingLeft: '14px', fontSize: '9px', color: COLORS.med, lineHeight: 1.55 }}>
<li>{de ? 'Ohne KI Wettbewerbsfähigkeit verloren — mit US-KI Datenkontrolle verloren' : 'Without AI: lose competitiveness — with US AI: lose data control'}</li>
<li>{de ? 'AI Act, CRA, NIS2 zwingen 30.000+ Firmen in komplexe Compliance' : 'AI Act, CRA, NIS2 force 30,000+ firms into complex compliance'}</li>
<li>{de ? 'Hohe Pentest-/Audit-Kosten, jährliche statt kontinuierliche Prüfung' : 'High pentest/audit cost, annual instead of continuous checks'}</li>
</ul>
</div>
<div style={{ border: `1px solid ${COLORS.border}`, borderRadius: '6px', padding: '10px', borderTop: `2px solid #10b981` }}>
<p style={{ fontSize: '10px', fontWeight: 700, color: '#16a34a', margin: '0 0 6px', textTransform: 'uppercase', letterSpacing: '0.04em' }}>{de ? 'Lösung' : 'Solution'}</p>
<ul style={{ margin: 0, paddingLeft: '14px', fontSize: '9px', color: COLORS.med, lineHeight: 1.55 }}>
<li>{de ? 'Jede Code-Änderung automatisch geprüft (SAST/DAST/SBOM/Pentest)' : 'Every code change auto-checked (SAST/DAST/SBOM/pentest)'}</li>
<li>{de ? 'VVT, TOMs, DSFA, CE-Risikobeurteilung in Echtzeit' : 'RoPA, TOMs, DPIA, CE risk assessment in real time'}</li>
<li>{de ? 'EU-Hosting (DE/FR), Audit-Ready zu jedem Zeitpunkt' : 'EU hosting (DE/FR), audit-ready at any time'}</li>
</ul>
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '10px' }}>
<div style={{ border: `1px solid ${COLORS.border}`, borderRadius: '6px', padding: '8px' }}>
<p style={{ fontSize: '9px', fontWeight: 700, color: '#a855f7', textTransform: 'uppercase', letterSpacing: '0.04em', margin: '0 0 4px' }}>{de ? 'Zielmärkte' : 'Target markets'}</p>
<p style={{ fontSize: '9px', color: COLORS.med, margin: 0, lineHeight: 1.5 }}>
{de ? 'Maschinen- & Anlagenbau · Automotive · Zulieferer · Produzierende Unternehmen' : 'Machine & plant manufacturing · Automotive · Suppliers · Manufacturing'}
</p>
</div>
<div style={{ border: `1px solid ${COLORS.border}`, borderRadius: '6px', padding: '8px' }}>
<p style={{ fontSize: '9px', fontWeight: 700, color: '#f59e0b', textTransform: 'uppercase', letterSpacing: '0.04em', margin: '0 0 4px' }}>{de ? 'Markt' : 'Market'}</p>
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '9px', color: COLORS.med }}><span>TAM</span><span>{tam ? fmtEur(tam.value_eur) : '—'}</span></div>
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '9px', color: COLORS.med }}><span>SAM</span><span>{sam ? fmtEur(sam.value_eur) : '—'}</span></div>
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '9px', color: COLORS.med }}><span>SOM</span><span>{som ? fmtEur(som.value_eur) : '—'}</span></div>
</div>
<div style={{ border: `1px solid ${COLORS.border}`, borderRadius: '6px', padding: '8px' }}>
<p style={{ fontSize: '9px', fontWeight: 700, color: '#10b981', textTransform: 'uppercase', letterSpacing: '0.04em', margin: '0 0 4px' }}>{de ? 'Kundenersparnis KMU/Jahr' : 'SME savings/year'}</p>
<p style={{ fontSize: '14px', fontWeight: 800, color: '#16a34a', margin: 0 }}>~55k EUR</p>
<p style={{ fontSize: '8px', color: COLORS.light, margin: '2px 0 0' }}>{de ? 'Pentests, CE, Compliance-Zeit, Audit' : 'Pentests, CE, compliance time, audit'}</p>
</div>
</div>
<div style={{ marginTop: 'auto', padding: '8px 12px', background: '#f0fdf4', border: '1px solid #bbf7d0', borderRadius: '6px', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<p style={{ fontSize: '9px', color: '#166534', margin: 0 }}>
<strong>{de ? 'The Ask:' : 'The Ask:'}</strong>{' '}
{(() => {
const amount = Number(funding?.amount_eur) || 0
const label = amount >= 1_000_000 ? `${(amount / 1_000_000).toFixed(1)} Mio. EUR` : `${Math.round(amount / 1000)}k EUR`
return `${label} ${funding?.instrument || 'Pre-Seed'} · ${funding?.round_name || 'Pre-Seed'}`
})()}
</p>
</div>
</div>
</PrintPage>
)
}
const USP_PILLARS = {
de: [
{ title: 'RFQ-Prüfung', body: 'Kunden-Anforderungsdokumente automatisch gegen Source-Code geprüft. Abweichungen erkannt, Änderungen vorgeschlagen.', stat: 'Antwortzeit 4,2h (war 12 Tage)' },
{ title: 'Prozess-Compliance', body: 'Vom Audit-Finding zum Ticket zur Code-Änderung — End-to-End automatisiert. Rollen, Fristen, Eskalation, Nachweise.', stat: '87% automatisierte Prozessschritte' },
{ title: 'Bidirektional', body: 'Compliance-Anforderungen fließen in den Code. Code-Änderungen aktualisieren die Compliance-Doku. Zero Drift.', stat: '0 Drift-Vorfälle seit März 2024' },
{ title: 'Kontinuierlich', body: 'Statt jährlicher Stichproben: Prüfung bei jeder Code-Änderung. Findings sofort zu Tickets mit Fix-Vorschlägen.', stat: '~2.400 Validierungen / Tag / Repo' },
],
en: [
{ title: 'RFQ Verification', body: 'Customer requirement docs automatically verified against current source code. Deviations detected, fixes proposed.', stat: 'Response time 4.2h (was 12 days)' },
{ title: 'Process Compliance', body: 'From audit finding to ticket to code change — fully automated. Roles, deadlines, escalation, evidence.', stat: '87% process steps automated' },
{ title: 'Bidirectional Sync', body: 'Compliance requirements flow into code. Code changes update compliance docs. Zero drift between worlds.', stat: '0 drift incidents since Mar-2024' },
{ title: 'Continuous, Not Yearly', body: 'Validation on every code change instead of annual checks. Findings as tickets with concrete fix proposals.', stat: '~2,400 validations / day / repo' },
],
}
const USP_HOOD = {
de: [
{ title: 'End-to-End Traceability', body: 'Gesetz → Obligation → Control deterministisch mit Systemzustand und Code verknüpft. Revisionssicherer Evidence-Layer.' },
{ title: 'Continuous Compliance Engine', body: 'Validierung bei jeder Änderung (Code/IaC/Prozesse) mit auditierbaren Nachweisen in Echtzeit. Rule-Packs pro Framework.' },
{ title: 'Compliance Optimizer', body: 'Maximal zulässige Ausgestaltung jedes KI-Use-Cases. Constraint-Optimierung statt nur erlaubt/verboten — spart 20200k EUR Anwaltskosten.' },
{ title: 'EU-Trust & Governance Stack', body: 'DSGVO · NIS-2 · DORA · EU AI Act · ISO 27001 · BSI C5 · EU-souveränes Hosting. Eine Plattform, ein Audit.' },
],
en: [
{ title: 'End-to-End Traceability', body: 'Law → Obligation → Control deterministically linked to system state and code. Audit-proof evidence layer.' },
{ title: 'Continuous Compliance Engine', body: 'Validation on every change (code/IaC/process) with auditable evidence in real time. Rule packs per framework.' },
{ title: 'Compliance Optimizer', body: 'Max permissible configuration of every AI use case. Constraint optimization beyond allowed/forbidden — replaces EUR 20200k legal fees.' },
{ title: 'EU Trust & Governance Stack', body: 'GDPR · NIS-2 · DORA · EU AI Act · ISO 27001 · BSI C5 · EU-sovereign hosting. One platform, one audit.' },
],
}
export function PrintUSPPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
const de = lang === 'de'
const pillars = de ? USP_PILLARS.de : USP_PILLARS.en
const hood = de ? USP_HOOD.de : USP_HOOD.en
return (
<PrintPage title="USP" pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<SectionTitle subtitle={de ? 'Vier Säulen Compliance + Code, vier technische Differenzierer' : 'Four pillars compliance + code, four technical differentiators'}>
{de ? 'Unsere USPs' : 'Our USPs'}
</SectionTitle>
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: '10px' }}>
<div>
<p style={{ fontSize: '9px', fontWeight: 700, color: COLORS.indigo, textTransform: 'uppercase', letterSpacing: '0.06em', margin: '0 0 6px' }}>{de ? 'Vier Säulen' : 'Four Pillars'}</p>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '8px' }}>
{pillars.map(p => (
<div key={p.title} style={{ border: `1px solid ${COLORS.border}`, borderRadius: '6px', padding: '10px', borderTop: `2px solid ${COLORS.indigo}` }}>
<p style={{ fontSize: '11px', fontWeight: 700, color: COLORS.dark, margin: '0 0 4px' }}>{p.title}</p>
<p style={{ fontSize: '9px', color: COLORS.med, margin: '0 0 6px', lineHeight: 1.5 }}>{p.body}</p>
<p style={{ fontSize: '8px', fontWeight: 700, color: COLORS.indigo, margin: 0, fontFamily: 'ui-monospace, monospace' }}>{p.stat}</p>
</div>
))}
</div>
</div>
<div>
<p style={{ fontSize: '9px', fontWeight: 700, color: COLORS.indigo, textTransform: 'uppercase', letterSpacing: '0.06em', margin: '0 0 6px' }}>{de ? 'Under the Hood' : 'Under the Hood'}</p>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '8px' }}>
{hood.map(h => (
<div key={h.title} style={{ border: `1px solid ${COLORS.border}`, borderRadius: '6px', padding: '10px' }}>
<p style={{ fontSize: '11px', fontWeight: 700, color: COLORS.dark, margin: '0 0 4px' }}>{h.title}</p>
<p style={{ fontSize: '9px', color: COLORS.med, margin: 0, lineHeight: 1.5 }}>{h.body}</p>
</div>
))}
</div>
</div>
<div style={{ marginTop: 'auto', padding: '10px 14px', background: '#f5f3ff', borderRadius: '6px', borderLeft: `3px solid ${COLORS.indigo}`, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<p style={{ fontSize: '10px', color: COLORS.med, margin: 0, fontStyle: 'italic', lineHeight: 1.5 }}>
{de
? '„Compliance ↔ Code · immer in Sync. Eine Plattform, eine geschlossene Schleife. Auditoren, Entwickler und Sales fragen denselben Graphen ab."'
: '"Compliance ↔ Code · always in sync. One platform, one closed loop. Auditors, engineers and sales all query the same graph."'}
</p>
</div>
</div>
</PrintPage>
)
}
const REG_KEY = [
{ 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' },
]
const REG_INDUSTRIES = [
{ de: 'Automobilindustrie', en: 'Automotive', regs: ['GDPR', 'AI_ACT', 'NIS2', 'CRA', 'MACHINERY_REG', 'DATA_ACT', 'BATTERIE_VO'], totalDocs: 263 },
{ de: 'Maschinen- & Anlagenbau', en: 'Machinery & Plant Eng.', regs: ['GDPR', 'AI_ACT', 'NIS2', 'CRA', 'MACHINERY_REG', 'DATA_ACT'], totalDocs: 266 },
{ de: 'Elektro- & Digitalindustrie', en: 'Electrical & Digital', regs: ['GDPR', 'AI_ACT', 'NIS2', 'CRA', 'MACHINERY_REG', 'DATA_ACT', 'BATTERIE_VO'], totalDocs: 281 },
{ de: 'Chemie- & Prozessindustrie', en: 'Chemicals & Process', regs: ['GDPR', 'AI_ACT', 'NIS2', 'CRA', 'DATA_ACT'], totalDocs: 250 },
{ de: 'Metallindustrie', en: 'Metal Industry', regs: ['GDPR', 'AI_ACT', 'NIS2', 'CRA', 'MACHINERY_REG', 'DATA_ACT'], totalDocs: 246 },
{ de: 'Energie & Versorgung', en: 'Energy & Utilities', regs: ['GDPR', 'AI_ACT', 'NIS2', 'CRA', 'DATA_ACT', 'BATTERIE_VO'], totalDocs: 256 },
{ de: 'Transport & Logistik', en: 'Transport & Logistics', regs: ['GDPR', 'AI_ACT', 'NIS2', 'CRA', 'DATA_ACT'], totalDocs: 256 },
{ de: 'Handel', en: 'Retail & Commerce', regs: ['GDPR', 'AI_ACT', 'NIS2', 'CRA', 'DATA_ACT'], totalDocs: 271 },
{ de: 'Konsumgüter & Lebensmittel', en: 'Consumer Goods & Food', regs: ['GDPR', 'AI_ACT', 'NIS2', 'CRA', 'DATA_ACT', 'BATTERIE_VO'], totalDocs: 265 },
{ de: 'Bauwirtschaft', en: 'Construction', regs: ['GDPR', 'AI_ACT', 'NIS2', 'CRA', 'MACHINERY_REG', 'DATA_ACT'], totalDocs: 245 },
]
export function PrintRegulatoryLandscapePage({ lang, pageNum, totalPages, versionName }: SlideBase) {
const de = lang === 'de'
const kpis = [
{ v: '380+', l: de ? 'Gesetze im RAG' : 'Laws in RAG' },
{ v: '244', l: de ? 'Horizontal' : 'Horizontal' },
{ v: '65', l: de ? 'Branchen-spezifisch' : 'Industry-specific' },
{ v: '10', l: de ? 'Branchen' : 'Industries' },
]
return (
<PrintPage title={de ? 'Regulatorische Landschaft' : 'Regulatory Landscape'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<SectionTitle subtitle={de ? '380+ Regularien, 10 Branchen — was wo gilt' : '380+ regulations, 10 industries — what applies where'}>
{de ? 'Regulatorische Landschaft' : 'Regulatory Landscape'}
</SectionTitle>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '8px', marginBottom: '10px' }}>
{kpis.map(k => (
<div key={k.l} style={{ background: COLORS.indigoLight, padding: '8px', borderRadius: '6px', textAlign: 'center', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<p style={{ fontSize: '16px', fontWeight: 800, color: COLORS.indigo, margin: 0 }}>{k.v}</p>
<p style={{ fontSize: '8px', color: COLORS.med, margin: '2px 0 0', textTransform: 'uppercase', letterSpacing: '0.04em' }}>{k.l}</p>
</div>
))}
</div>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '9px' }}>
<thead>
<tr style={{ background: COLORS.indigoLight, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<th style={{ padding: '5px 7px', textAlign: 'left', fontSize: '8px', textTransform: 'uppercase', letterSpacing: '0.04em', color: COLORS.dark }}>{de ? 'Branche' : 'Industry'}</th>
{REG_KEY.map(r => (
<th key={r.id} style={{ padding: '5px 4px', textAlign: 'center', fontSize: '8px', textTransform: 'uppercase', color: r.color, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>{r.label}</th>
))}
<th style={{ padding: '5px 7px', textAlign: 'right', fontSize: '8px', textTransform: 'uppercase', letterSpacing: '0.04em', color: COLORS.indigo }}>{de ? 'Gesetze' : 'Laws'}</th>
</tr>
</thead>
<tbody>
{REG_INDUSTRIES.map((ind, i) => (
<tr key={ind.de} style={{ background: i % 2 === 0 ? '#ffffff' : '#fafafa' }}>
<td style={{ padding: '5px 7px', color: COLORS.dark, fontWeight: 500, borderBottom: `1px solid ${COLORS.border}` }}>{de ? ind.de : ind.en}</td>
{REG_KEY.map(r => (
<td key={r.id} style={{ padding: '5px 4px', textAlign: 'center', borderBottom: `1px solid ${COLORS.border}` }}>
{ind.regs.includes(r.id)
? <span style={{ display: 'inline-block', width: '8px', height: '8px', borderRadius: '99px', background: r.color, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
: <span style={{ color: COLORS.border }}>·</span>}
</td>
))}
<td style={{ padding: '5px 7px', textAlign: 'right', fontWeight: 700, color: COLORS.dark, borderBottom: `1px solid ${COLORS.border}` }}>{ind.totalDocs}</td>
</tr>
))}
</tbody>
</table>
<p style={{ fontSize: '8px', color: COLORS.light, margin: '8px 0 0', fontStyle: 'italic' }}>
{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>
</PrintPage>
)
}
const HIW_STEPS_DE = [
{ n: '01', t: 'Cloud-Vertrag abschließen', d: 'BSI-zertifizierte Cloud in Deutschland. Fixe oder flexible Kosten.' },
{ n: '02', t: 'Code-Repos verbinden', d: 'Git-Repos, CI/CD Pipelines und Firmware-Projekte anbinden. Die KI scannt automatisch auf Schwachstellen und Compliance-Lücken bei jeder Änderung.' },
{ n: '03', t: 'Compliance & Security automatisieren', d: 'Kontinuierliche Code-Analyse, Pentesting und Risikoanalysen. VVT, TOMs, DSFA und CE-Dokumentation werden automatisch erstellt und aktualisiert.' },
{ n: '04', t: 'Audit vorbereiten', d: 'Alle Nachweise, Dokumente und Risikobeurteilungen auf Knopfdruck. Abweichungen nach dem Audit automatisch nachverfolgen mit Stichtagen und Eskalation.' },
]
const HIW_STEPS_EN = [
{ n: '01', t: 'Sign Cloud Contract', d: 'BSI-certified cloud in Germany. Fixed or flexible costs.' },
{ n: '02', t: 'Connect Code Repos', d: 'Connect Git repos, CI/CD pipelines and firmware projects. The AI scans automatically for vulnerabilities and compliance gaps on every change.' },
{ n: '03', t: 'Automate Compliance & Security', d: 'Continuous code analysis, pentesting and risk assessments. RoPA, TOMs, DPIA and CE documentation are automatically created and updated.' },
{ n: '04', t: 'Prepare for Audit', d: 'All evidence, documents and risk assessments at the push of a button. Post-audit deviations automatically tracked with deadlines and escalation.' },
]
export function PrintHowItWorksPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
const de = lang === 'de'
const steps = de ? HIW_STEPS_DE : HIW_STEPS_EN
return (
<PrintPage title={de ? 'So funktioniert\'s' : 'How It Works'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<SectionTitle subtitle={de ? 'In 4 Schritten zur kontinuierlichen Compliance' : 'Continuous compliance in 4 steps'}>
{de ? 'So funktioniert\'s' : 'How It Works'}
</SectionTitle>
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: '14px' }}>
{steps.map((s, idx) => (
<div key={s.n} style={{ display: 'flex', gap: '14px', alignItems: 'flex-start', borderLeft: `3px solid ${COLORS.indigo}`, paddingLeft: '14px' }}>
<div style={{ width: '44px', height: '44px', borderRadius: '8px', background: COLORS.indigoLight, color: COLORS.indigo, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '15px', fontWeight: 800, flexShrink: 0, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
{s.n}
</div>
<div style={{ flex: 1 }}>
<p style={{ fontSize: '15px', fontWeight: 700, color: COLORS.dark, margin: '0 0 4px' }}>{s.t}</p>
<p style={{ fontSize: '11px', color: COLORS.med, margin: 0, lineHeight: 1.55 }}>{s.d}</p>
</div>
{idx < steps.length - 1 && <span style={{ position: 'absolute' }} />}
</div>
))}
</div>
</PrintPage>
)
}
const BM_TIERS_DE = [
{ name: 'Starter', target: 'Startups & Kleinstunternehmen', emp: '< 10', price: '3.600 EUR/Jahr', features: ['Code Security (SAST/DAST)', 'Compliance-Dokumente', 'Consent Management', '1 Anwendung'], highlight: false },
{ name: 'Professional', target: 'KMU & Mittelstand', emp: '10 250', price: '15.000 40.000 EUR/Jahr', features: ['Alle Module inkl. CE-Bewertung', 'Audit Manager End-to-End', 'AI Act Compliance (UCCA)', 'Unbegrenzte Anwendungen'], highlight: true },
{ name: 'Enterprise', target: 'Konzerne & OEMs', emp: '250+', price: 'ab 50.000 EUR/Jahr', features: ['Dedizierte Instanz', 'Custom Integrationen (SAP, MES)', 'SLA & Priority Support', 'Tender Matching & RFQ-Prüfung'], highlight: false },
]
const BM_TIERS_EN = [
{ name: 'Starter', target: 'Startups & Micro', emp: '< 10', price: '3,600 EUR/yr', features: ['Code Security (SAST/DAST)', 'Compliance documents', 'Consent management', '1 application'], highlight: false },
{ name: 'Professional', target: 'SME & Mid-Market', emp: '10 250', price: '15,000 40,000 EUR/yr', features: ['All modules incl. CE assessment', 'Audit Manager end-to-end', 'AI Act Compliance (UCCA)', 'Unlimited applications'], highlight: true },
{ name: 'Enterprise', target: 'Enterprises & OEMs', emp: '250+', price: 'from 50,000 EUR/yr', features: ['Dedicated instance', 'Custom integrations (SAP, MES)', 'SLA & priority support', 'Tender matching & RFQ verification'], highlight: false },
]
export function PrintBusinessModelPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
const de = lang === 'de'
const tiers = de ? BM_TIERS_DE : BM_TIERS_EN
return (
<PrintPage title={de ? 'Geschäftsmodell' : 'Business Model'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<SectionTitle subtitle={de ? 'Drei Tiers — modular, jährliche Subscription, kein Setup-Fee' : 'Three tiers — modular, annual subscription, no setup fee'}>
{de ? 'Geschäftsmodell' : 'Business Model'}
</SectionTitle>
<div style={{ flex: 1, display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '12px' }}>
{tiers.map(t => (
<div key={t.name} style={{
border: `1px solid ${t.highlight ? COLORS.indigo : COLORS.border}`,
borderRadius: '10px',
padding: '16px',
background: t.highlight ? '#f5f3ff' : '#fff',
display: 'flex',
flexDirection: 'column',
WebkitPrintColorAdjust: 'exact',
printColorAdjust: 'exact',
borderTop: `3px solid ${t.highlight ? COLORS.indigo : '#cbd5e1'}`,
}}>
<p style={{ fontSize: '18px', fontWeight: 800, color: COLORS.dark, margin: '0 0 2px' }}>{t.name}</p>
<p style={{ fontSize: '10px', color: COLORS.med, margin: '0 0 2px' }}>{t.target}</p>
<p style={{ fontSize: '9px', color: COLORS.light, margin: '0 0 14px' }}>{t.emp} {de ? 'Mitarbeiter' : 'employees'}</p>
<p style={{ fontSize: '20px', fontWeight: 800, color: t.highlight ? COLORS.indigo : COLORS.dark, margin: '0 0 14px' }}>{t.price}</p>
<ul style={{ margin: 0, paddingLeft: '0', listStyle: 'none', flex: 1 }}>
{t.features.map(f => (
<li key={f} style={{ fontSize: '10px', color: COLORS.med, padding: '4px 0 4px 14px', position: 'relative', lineHeight: 1.4 }}>
<span style={{ position: 'absolute', left: 0, top: '8px', width: '5px', height: '5px', borderRadius: '99px', background: COLORS.indigo, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
{f}
</li>
))}
</ul>
</div>
))}
</div>
</PrintPage>
)
}
const COMP_COMPETITORS = [
{ name: 'Vanta', flag: 'US', founded: 2018, emp: '1.695', revenue: '$220M ARR', customers: '12.000', pricing: '$10K80K/yr', ai: 'full' as const },
{ name: 'Drata', flag: 'US', founded: 2020, emp: '732', revenue: '$100M ARR', customers: '8.000', pricing: '$10K100K/yr', ai: 'full' as const },
{ name: 'Sprinto', flag: 'IN', founded: 2020, emp: '316', revenue: '$38M ARR', customers: '3.000', pricing: '$6K25K/yr', ai: 'full' as const },
{ name: 'DataGuard', flag: 'DE', founded: 2017, emp: '250', revenue: '~€52M', customers: '4.000', pricing: '€6K24K+/yr', ai: 'partial' as const },
{ name: 'Proliance', flag: 'DE', founded: 2017, emp: '65', revenue: '~€3.9M', customers: '2.000', pricing: '€1.5K5.7K/yr', ai: 'none' as const },
{ name: 'heyData', flag: 'DE', founded: 2020, emp: '58', revenue: '~€15M', customers: '2.000', pricing: '€1K3.8K/yr', ai: 'partial' as const },
]
const COMP_USP_ROWS_DE = ['Code-Security + DevSecOps (6 Tools, SAST/DAST/SBOM/Container/Secrets/IaC)', 'LLM-Auto-Fix für gefundene Schwachstellen', 'Firmware & Embedded-Security', 'PII-Redaction LLM Gateway', 'RAG mit 25.000+ Sicherheitskontrollen', 'AI Act und CRA Compliance End-to-End', 'CE-Software-Risikobeurteilung nach Maschinen-VO', 'Whistleblower-Portal (HinSchG)', 'Maschinenbau-Branchenfokus', 'Self-Hosted / On-Premise möglich']
const COMP_USP_ROWS_EN = ['Code security + DevSecOps (6 tools, SAST/DAST/SBOM/container/secrets/IaC)', 'LLM auto-fix for detected vulnerabilities', 'Firmware & embedded security', 'PII redaction LLM gateway', 'RAG with 25,000+ security controls', 'AI Act and CRA compliance end-to-end', 'CE software risk assessment per Machinery Regulation', 'Whistleblower portal (HinSchG)', 'Manufacturing industry focus', 'Self-hosted / on-premise possible']
export function PrintCompetitionPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
const de = lang === 'de'
const aiLabel = (a: 'full' | 'partial' | 'none') => a === 'full' ? (de ? 'Voll' : 'Full') : a === 'partial' ? (de ? 'Teil' : 'Partial') : (de ? 'Keine' : 'None')
const aiColor = (a: 'full' | 'partial' | 'none') => a === 'full' ? '#16a34a' : a === 'partial' ? '#d97706' : '#94a3b8'
return (
<PrintPage title={de ? 'Wettbewerb' : 'Competition'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<SectionTitle subtitle={de ? 'Niemand verbindet Code-Security mit Compliance-Automatisierung — wir schon' : 'Nobody combines code security with compliance automation — we do'}>
{de ? 'Wettbewerb' : 'Competition'}
</SectionTitle>
<div style={{ display: 'grid', gridTemplateColumns: '1.4fr 1fr', gap: '12px', flex: 1 }}>
<div>
<p style={{ fontSize: '9px', fontWeight: 700, color: COLORS.indigo, textTransform: 'uppercase', letterSpacing: '0.06em', margin: '0 0 6px' }}>{de ? 'Wettbewerber-Übersicht' : 'Competitor Overview'}</p>
<PrintTable
headers={[de ? 'Name' : 'Name', de ? 'Gegr.' : 'Est.', 'MA', de ? 'Umsatz' : 'Revenue', de ? 'Kunden' : 'Customers', 'Pricing', 'KI']}
rows={COMP_COMPETITORS.map(c => [
<span key="n" style={{ fontWeight: 700 }}>{c.flag} {c.name}</span>,
c.founded.toString(),
c.emp,
c.revenue,
c.customers,
c.pricing,
<span key="ai" style={{ color: aiColor(c.ai), fontWeight: 700 }}>{aiLabel(c.ai)}</span>,
])}
colWidths={['18%', '8%', '10%', '15%', '12%', '20%', '10%']}
/>
</div>
<div>
<p style={{ fontSize: '9px', fontWeight: 700, color: COLORS.indigo, textTransform: 'uppercase', letterSpacing: '0.06em', margin: '0 0 6px' }}>{de ? 'Was nur BreakPilot hat' : 'BreakPilot-only features'}</p>
<ul style={{ margin: 0, paddingLeft: 0, listStyle: 'none' }}>
{(de ? COMP_USP_ROWS_DE : COMP_USP_ROWS_EN).map(r => (
<li key={r} style={{ fontSize: '9px', color: COLORS.med, padding: '4px 0 4px 16px', position: 'relative', lineHeight: 1.45, borderBottom: `1px solid ${COLORS.border}` }}>
<span style={{ position: 'absolute', left: 0, top: '7px', width: '8px', height: '8px', borderRadius: '99px', background: COLORS.indigo, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
{r}
</li>
))}
</ul>
</div>
</div>
<p style={{ fontSize: '8px', color: COLORS.light, margin: '8px 0 0', fontStyle: 'italic' }}>
{de
? 'Weitere DACH-Anbieter: Secjur, Usercentrics, Caralegal, 2B Advice, OneTrust. Keiner kombiniert DSGVO + Code-Security + Self-Hosted KI.'
: 'Other DACH players: Secjur, Usercentrics, Caralegal, 2B Advice, OneTrust. None combines GDPR + code security + self-hosted AI.'}
</p>
</PrintPage>
)
}
const SAVINGS_DE = [
{ name: 'KMU (25 MA)', bp: '15.000 EUR/Jahr', without: '86.000', with: '31.000', save: '55.000', roi: '3,7x' },
{ name: 'Mittelstand (100 MA)', bp: '30.000 EUR/Jahr', without: '291.000', with: '98.000', save: '193.000', roi: '6,4x' },
{ name: 'Konzern (500+ MA)', bp: '50.000 EUR/Jahr', without: '1.190.000', with: '410.000', save: '780.000', roi: '15,6x' },
]
const SAVINGS_EN = [
{ name: 'SME (25 emp.)', bp: 'EUR 15,000/yr', without: '86,000', with: '31,000', save: '55,000', roi: '3.7x' },
{ name: 'Mid-size (100 emp.)', bp: 'EUR 30,000/yr', without: '291,000', with: '98,000', save: '193,000', roi: '6.4x' },
{ name: 'Enterprise (500+ emp.)', bp: 'EUR 50,000/yr', without: '1,190,000', with: '410,000', save: '780,000', roi: '15.6x' },
]
const SAVINGS_LINES_DE = ['Pentests (Anwendungen)', 'CE-SW-Risikobeurteilung', 'Compliance-Dokumentation', 'Produktivere Compliance-Arbeitszeit', 'Audit-Vorbereitung', 'Externe Berater / FTE / Strafvermeidung']
const SAVINGS_LINES_EN = ['Pentests (applications)', 'CE SW risk assessment', 'Compliance documentation', 'More productive compliance time', 'Audit preparation', 'External consultants / FTE / penalty avoidance']
export function PrintCustomerSavingsPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
const de = lang === 'de'
const rows = (de ? SAVINGS_DE : SAVINGS_EN).map(r => [
<strong key="n">{r.name}</strong>,
r.bp,
<span key="w" style={{ color: '#dc2626' }}>{r.without} </span>,
<span key="m" style={{ color: COLORS.med }}>{r.with} </span>,
<span key="s" style={{ color: '#16a34a', fontWeight: 700 }}>{r.save} </span>,
<span key="r" style={{ color: COLORS.indigo, fontWeight: 700 }}>{r.roi}</span>,
])
return (
<PrintPage title={de ? 'Kundenersparnis' : 'Customer Savings'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<SectionTitle subtitle={de ? 'Pentests, CE-Beurteilungen, FTE-Aufwand — Kosten skalieren linear, unsere Plattform nicht' : 'Pentests, CE assessments, FTE effort — costs scale linearly, our platform does not'}>
{de ? 'Kundenersparnis im Detail' : 'Customer Savings in Detail'}
</SectionTitle>
<div style={{ marginBottom: '10px' }}>
<PrintTable
headers={[de ? 'Kundenprofil' : 'Customer profile', de ? 'BreakPilot' : 'BreakPilot', de ? 'Ohne' : 'Without', de ? 'Mit' : 'With', de ? 'Ersparnis/Jahr' : 'Savings/year', 'ROI']}
rows={rows}
colWidths={['25%', '18%', '15%', '12%', '20%', '10%']}
/>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px', flex: 1 }}>
<div style={{ border: `1px solid ${COLORS.border}`, borderRadius: '6px', padding: '10px' }}>
<p style={{ fontSize: '9px', fontWeight: 700, color: COLORS.indigo, textTransform: 'uppercase', letterSpacing: '0.04em', margin: '0 0 6px' }}>{de ? 'Wo gespart wird' : 'Where savings come from'}</p>
<ul style={{ margin: 0, paddingLeft: '14px', fontSize: '10px', color: COLORS.med, lineHeight: 1.55 }}>
{(de ? SAVINGS_LINES_DE : SAVINGS_LINES_EN).map(l => <li key={l}>{l}</li>)}
</ul>
</div>
<div style={{ background: '#f0fdf4', border: '1px solid #bbf7d0', borderRadius: '6px', padding: '10px', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<p style={{ fontSize: '9px', fontWeight: 700, color: '#166534', textTransform: 'uppercase', letterSpacing: '0.04em', margin: '0 0 6px' }}>{de ? 'Versteckter Hebel' : 'Hidden lever'}</p>
<p style={{ fontSize: '10px', color: '#166534', margin: 0, lineHeight: 1.55, fontStyle: 'italic' }}>
{de
? '„Der größte versteckte Kostentreiber ist Entwickler-Produktivität: ohne automatisierte Security-Tools verbringen Entwickler 19% ihrer Arbeitszeit mit Sicherheitsaufgaben statt mit Features." — IDC'
: '"The largest hidden cost driver is developer productivity: without automated security tools, developers spend 19% of their time on security tasks instead of features." — IDC'}
</p>
</div>
</div>
<div style={{ marginTop: '10px', display: 'flex', gap: '6px', flexWrap: 'wrap' }}>
{(de ? ['Pentests', 'CE-Risiko', 'Compliance-Zeit', 'Audit-Vorb.', 'Strafvermeidung'] : ['Pentests', 'CE risk', 'Compliance time', 'Audit prep', 'Penalty avoidance']).map(b => <Badge key={b}>{b}</Badge>)}
</div>
</PrintPage>
)
}
@@ -1,5 +1,7 @@
import { PrintPage, SectionTitle, PrintTable, Badge, COLORS } from './PrintLayout'
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 }
@@ -34,121 +36,210 @@ export function aggregateAnnualRows(results: FMResult[]): AnnualPLRow[] {
const gross = revenue - cogs
const ebitda = gross - personnel - marketing - infra
return {
year,
revenue_eur: revenue,
cogs_eur: cogs,
gross_profit_eur: gross,
personnel_eur: personnel,
marketing_eur: marketing,
infra_eur: infra,
ebitda_eur: ebitda,
total_customers: last?.total_customers ?? 0,
employees_count: last?.employees_count ?? 0,
year, revenue_eur: revenue, cogs_eur: cogs, gross_profit_eur: gross,
personnel_eur: personnel, marketing_eur: marketing, infra_eur: infra, ebitda_eur: ebitda,
total_customers: last?.total_customers ?? 0, employees_count: last?.employees_count ?? 0,
}
})
}
function fmtEur(n: number) {
function fmtEur(n: number, dense = false): string {
const abs = Math.abs(n)
if (abs >= 1_000_000) return `${(n / 1_000_000).toLocaleString('de-DE', { maximumFractionDigits: 1 })}M`
if (abs >= 1_000) return `${(n / 1_000).toLocaleString('de-DE', { maximumFractionDigits: 0 })}k`
return n.toLocaleString('de-DE', { maximumFractionDigits: 0 })
const sign = n < 0 ? '' : ''
if (abs >= 1e6) return `${sign}${(abs / 1e6).toLocaleString('de-DE', { maximumFractionDigits: dense ? 1 : 2 })}M`
if (abs >= 1e3) return `${sign}${(abs / 1e3).toLocaleString('de-DE', { maximumFractionDigits: 0 })}k`
return `${sign}${abs.toLocaleString('de-DE', { maximumFractionDigits: 0 })}`
}
function colorEur(n: number) { return n >= 0 ? '#16a34a' : '#dc2626' }
/* ===== FINANZPLAN, PAGE 1: P&L 2026-2030 ===== */
export function PrintFinancialsPage({ annualRows, lang, pageNum, totalPages, versionName }: SlideBase & { annualRows: AnnualPLRow[] }) {
export function PrintFinanzplanPage1({ fmResults, lang, pageNum, totalPages, versionName }: SlideBase & { fmResults: FMResult[] }) {
const de = lang === 'de'
const headers = [
de ? 'Jahr' : 'Year',
de ? 'Umsatz' : 'Revenue',
de ? 'Rohertrag' : 'Gross Profit',
de ? 'Personal' : 'Personnel',
de ? 'Marketing' : 'Marketing',
'Infra',
'EBITDA',
de ? 'Kunden' : 'Customers',
de ? 'MA' : 'FTE',
]
const rows = annualRows.map(r => [
<strong key="y">{r.year}</strong>,
<span key="rev" style={{ color: COLORS.dark }}>{fmtEur(r.revenue_eur)}</span>,
fmtEur(r.gross_profit_eur),
`(${fmtEur(r.personnel_eur)})`,
`(${fmtEur(r.marketing_eur)})`,
`(${fmtEur(r.infra_eur)})`,
<span key="ebitda" style={{ fontWeight: 700, color: colorEur(r.ebitda_eur) }}>{fmtEur(r.ebitda_eur)}</span>,
r.total_customers.toString(),
r.employees_count.toString(),
])
const finalYear = annualRows[annualRows.length - 1]
const breakEvenYear = annualRows.find(r => r.ebitda_eur > 0)?.year
const rows = aggregateAnnualRows(fmResults)
if (rows.length === 0) {
return (
<Page kicker="17" section={de ? 'ANHANG · FINANZPLAN · 1/2' : 'APPENDIX · FINANCIAL PLAN · 1/2'} title={de ? 'Finanzplan nicht verfügbar' : 'Financial plan unavailable'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<p style={{ fontSize: '10pt', color: COLORS.slate600 }}>{de ? 'Keine Finanzdaten vorhanden. Bitte Base-Case-Szenario auswählen.' : 'No financial data available. Please select base-case scenario.'}</p>
</Page>
)
}
const years = rows.map(r => r.year)
const dataRow = (label: string, values: number[], bold = false, sign: '+' | '-' | '' = '') => {
const cells: (string | React.ReactNode)[] = [label]
values.forEach(v => cells.push(<span style={{ fontWeight: bold ? 800 : 500, color: bold ? COLORS.slate900 : COLORS.slate700, fontVariantNumeric: 'tabular-nums' }}>{sign === '-' ? `(${fmtEur(v)})` : fmtEur(v)}</span>))
return cells
}
const breakEvenYear = rows.find(r => r.ebitda_eur > 0)?.year
return (
<PrintPage title={de ? 'Finanzprognose' : 'Financial Projections'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<SectionTitle subtitle={de ? 'AI-First Kostenstruktur — skaliert ohne lineares Personalwachstum' : 'AI-First cost structure — scales without linear headcount growth'}>
{de ? 'Finanzprognose (Planzahlen)' : 'Financial Projections (Plan)'}
</SectionTitle>
<div style={{ marginTop: '10px' }}>
<PrintTable headers={headers} rows={rows} colWidths={['8%', '12%', '12%', '11%', '11%', '9%', '12%', '11%', '6%']} />
<Page kicker="17" section={de ? 'ANHANG · FINANZPLAN · 1/2' : 'APPENDIX · FINANCIAL PLAN · 1/2'} title={de ? 'Gewinn- und Verlustrechnung 20262030.' : 'Profit & Loss Statement 20262030.'} subtitle={de ? 'Base-Case-Szenario · alle Werte in EUR · konsolidierte Jahreswerte · Break-Even erwartet ' + (breakEvenYear ?? 'Q3 2029') : 'Base-case scenario · all values in EUR · consolidated annual values · break-even expected ' + (breakEvenYear ?? 'Q3 2029')} pageNum={pageNum} totalPages={totalPages} versionName={versionName} footnote={de ? 'In Klammern () = Kosten · Base-Case · L-Bank Finanzplan-Mapping (SKR04-Klassen 4/5/6/7) · Stand: ' + versionName : 'Parentheses () = costs · Base case · L-Bank financial plan mapping (SKR04 classes 4/5/6/7) · As of: ' + versionName}>
<DataTable
cols={[
{ header: de ? 'Position' : 'Position', width: '24%' },
...years.map(y => ({ header: String(y), numeric: true, width: '13%' as string })),
{ header: 'CAGR', numeric: true, width: '11%' as string },
]}
rows={[
dataRow(de ? 'Umsatz (SaaS)' : 'Revenue (SaaS)', rows.map(r => r.revenue_eur), true, '+').concat([
rows[0].revenue_eur > 0 ? `+${Math.round((Math.pow(rows[rows.length - 1].revenue_eur / Math.max(rows[0].revenue_eur, 1), 1 / (rows.length - 1)) - 1) * 100)}%` : '—',
]),
dataRow(de ? 'Infrastruktur (Cloud/LLM)' : 'Infrastructure (Cloud/LLM)', rows.map(r => r.infra_eur), false, '-').concat(['']),
dataRow(de ? 'Marketing & Vertrieb' : 'Marketing & Sales', rows.map(r => r.marketing_eur), false, '-').concat(['']),
dataRow(de ? 'Personal (inkl. GF)' : 'Personnel (incl. C-suite)', rows.map(r => r.personnel_eur), false, '-').concat(['']),
dataRow('COGS (5xxx)', rows.map(r => r.cogs_eur), false, '-').concat(['']),
[<strong style={{ color: COLORS.slate900 }} key="ge">{de ? 'Gesamtaufwand' : 'Total OpEx'}</strong>,
...rows.map(r => <strong style={{ color: COLORS.slate900, fontVariantNumeric: 'tabular-nums' }}>({fmtEur(r.cogs_eur + r.personnel_eur + r.marketing_eur + r.infra_eur)})</strong>),
'',
],
[<strong style={{ color: COLORS.slate900 }} key="eb">EBITDA</strong>,
...rows.map(r => (
<strong style={{ color: r.ebitda_eur >= 0 ? COLORS.emerald700 : COLORS.red700, fontVariantNumeric: 'tabular-nums', fontWeight: 800 }}>{fmtEur(r.ebitda_eur)}</strong>
)),
'',
],
[de ? 'EBITDA-Marge' : 'EBITDA margin',
...rows.map(r => <span style={{ color: r.ebitda_eur >= 0 ? COLORS.emerald700 : COLORS.red700, fontVariantNumeric: 'tabular-nums' }}>{r.revenue_eur > 0 ? Math.round(r.ebitda_eur / r.revenue_eur * 100) + '%' : '—'}</span>),
'',
],
[<span style={{ fontWeight: 600 }} key="mh"></span>, ...years.map(() => ''), ''],
[de ? 'Kunden (Dez)' : 'Customers (Dec)', ...rows.map(r => r.total_customers.toLocaleString('de-DE')), ''],
[de ? 'Mitarbeiter (Dez)' : 'Employees (Dec)', ...rows.map(r => r.employees_count.toLocaleString('de-DE')), ''],
[de ? 'Umsatz / Mitarbeiter' : 'Revenue / employee',
...rows.map(r => r.employees_count > 0 ? fmtEur(Math.round(r.revenue_eur / r.employees_count)) : '—'),
'',
],
]}
highlightFirstCol
/>
<div style={{ marginTop: '5mm', display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '4mm', flexShrink: 0 }}>
{[
{ l: de ? 'Umsatz 2030' : 'Revenue 2030', v: fmtEur(rows[rows.length - 1].revenue_eur, true), tone: COLORS.indigo600 },
{ l: 'EBITDA 2030', v: fmtEur(rows[rows.length - 1].ebitda_eur, true), tone: rows[rows.length - 1].ebitda_eur >= 0 ? COLORS.emerald700 : COLORS.red700 },
{ l: de ? 'Break-Even' : 'Break-even', v: String(breakEvenYear ?? 'Q3 2029'), tone: COLORS.emerald700 },
{ l: de ? 'Kunden 2030' : 'Customers 2030', v: rows[rows.length - 1].total_customers.toLocaleString('de-DE'), tone: COLORS.slate900 },
].map((k, i) => (
<div key={i} style={{ border: `1px solid ${COLORS.slate200}`, padding: '3mm' }}>
<div style={{ fontSize: '7pt', color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.08em', fontWeight: 600 }}>{k.l}</div>
<div style={{ fontSize: '18pt', fontWeight: 800, color: k.tone, lineHeight: 1, fontVariantNumeric: 'tabular-nums', marginTop: '1.5mm' }}>{k.v}</div>
</div>
))}
</div>
{(finalYear || breakEvenYear) && (
<div style={{ marginTop: '12px', display: 'flex', gap: '12px' }}>
{finalYear && <div style={{ padding: '6px 12px', background: COLORS.indigoLight, borderRadius: '6px', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<p style={{ fontSize: '8px', color: COLORS.light, margin: '0 0 2px' }}>{de ? 'ARR (letztes Jahr)' : 'ARR (final year)'}</p>
<p style={{ fontSize: '12px', fontWeight: 700, color: COLORS.indigo, margin: 0 }}>{fmtEur(finalYear.revenue_eur)}</p>
</div>}
{finalYear && <div style={{ padding: '6px 12px', background: '#f0fdf4', borderRadius: '6px' }}>
<p style={{ fontSize: '8px', color: COLORS.light, margin: '0 0 2px' }}>{de ? 'Kunden (letztes Jahr)' : 'Customers (final year)'}</p>
<p style={{ fontSize: '12px', fontWeight: 700, color: '#16a34a', margin: 0 }}>{finalYear.total_customers}</p>
</div>}
{breakEvenYear && <div style={{ padding: '6px 12px', background: '#fefce8', borderRadius: '6px' }}>
<p style={{ fontSize: '8px', color: COLORS.light, margin: '0 0 2px' }}>{de ? 'Break-Even' : 'Break-Even'}</p>
<p style={{ fontSize: '12px', fontWeight: 700, color: '#854d0e', margin: 0 }}>{breakEvenYear}</p>
</div>}
</div>
)}
<p style={{ fontSize: '8px', color: COLORS.light, marginTop: '8px' }}>
{de ? '* Planzahlen · Szenario: Base Case · In Klammern = Kosten' : '* Projections · Scenario: Base Case · Parentheses = costs'}
</p>
</PrintPage>
</Page>
)
}
/* ===== FINANZPLAN, PAGE 2: KPI dashboard + charts ===== */
export function PrintFinanzplanPage2({ fmResults, lang, pageNum, totalPages, versionName }: SlideBase & { fmResults: FMResult[] }) {
const de = lang === 'de'
const kpis = computeAnnualKPIs(fmResults)
if (kpis.length === 0) {
return <Page kicker="17" section={de ? 'ANHANG · FINANZPLAN · 2/2' : 'APPENDIX · FINANCIAL PLAN · 2/2'} title={de ? 'KPI-Dashboard nicht verfügbar' : 'KPI dashboard unavailable'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<p style={{ fontSize: '10pt', color: COLORS.slate600 }}>{de ? 'Keine Finanzdaten vorhanden.' : 'No financial data available.'}</p>
</Page>
}
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 (
<Page kicker="17" section={de ? 'ANHANG · FINANZPLAN · 2/2' : 'APPENDIX · FINANCIAL PLAN · 2/2'} title={de ? 'KPI-Dashboard und Wachstumskurve.' : 'KPI dashboard and growth trajectory.'} subtitle={de ? '19 KPIs pro Jahr aus den fp_*-Tabellen abgeleitet. Keine hardcodierten Werte. Base-Case-Szenario.' : '19 KPIs per year derived from fp_* tables. No hardcoded values. Base-case scenario.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
{/* Compact KPI table */}
<div style={{ marginBottom: '5mm' }}>
<DataTable
dense
cols={[
{ header: de ? 'KPI' : 'KPI', width: '22%' },
...kpis.map(k => ({ header: String(k.year), numeric: true })),
]}
rows={[
['ARR (Dez)', ...kpis.map(k => fmtEur(k.arr))],
[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 · Marge' : 'EBIT · margin', ...kpis.map(k => <span key="e" style={{ color: k.ebit >= 0 ? COLORS.emerald700 : COLORS.red700, fontWeight: 700 }}>{fmtEur(k.ebit)} · {k.ebitMargin}%</span>)],
[de ? 'Netto-Ergebnis' : 'Net income', ...kpis.map(k => <strong key="ni" style={{ color: k.netIncome >= 0 ? COLORS.emerald700 : COLORS.red700 }}>{fmtEur(k.netIncome)}</strong>)],
[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
/>
</div>
{/* Charts grid 2x2 */}
<div style={{ flex: 1, minHeight: 0, display: 'grid', gridTemplateColumns: '1fr 1fr', gridTemplateRows: '1fr 1fr', gap: '5mm 8mm' }}>
<BarChart
title={de ? 'Umsatz (€ Mio.)' : 'Revenue (€M)'}
data={kpis.map(k => ({ label: String(k.year), value: k.totalRevenue, tone: 'default' }))}
height={26}
formatValue={pickFmt(Math.max(...kpis.map(k => k.totalRevenue)))}
/>
<BarChart
title={de ? 'EBIT (€)' : 'EBIT (€)'}
data={kpis.map(k => ({ 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))))}
/>
<LineChart
title={de ? 'Cash-Bestand (€)' : 'Cash balance (€)'}
data={kpis.map(k => ({ 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
/>
<BarChart
title={de ? 'Mitarbeiter · FTE' : 'Employees · FTE'}
data={kpis.map(k => ({ label: String(k.year), value: k.employees, tone: 'accent' }))}
height={26}
formatValue={(n) => String(Math.round(n))}
/>
</div>
</Page>
)
}
/* ===== ASSUMPTIONS ===== */
export function PrintAssumptionsPage({ assumptions, lang, pageNum, totalPages, versionName }: SlideBase & { assumptions: FMAssumption[] }) {
const de = lang === 'de'
const scalars = assumptions
.filter(a => a.value_type === 'scalar')
.sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0))
.slice(0, 28)
const byCategory = scalars.reduce<Record<string, FMAssumption[]>>((acc, a) => {
const cat = a.category || 'General'
if (!acc[cat]) acc[cat] = []
acc[cat].push(a)
return acc
}, {})
const categories = Object.entries(byCategory)
const MONO = "'JetBrains Mono', ui-monospace, monospace"
return (
<PrintPage title={de ? 'Annahmen' : 'Assumptions'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<SectionTitle subtitle={de ? 'Base Case Szenario — Skalare Annahmen' : 'Base Case Scenario — Scalar Assumptions'}>
{de ? 'Finanzielle Annahmen' : 'Financial Assumptions'}
</SectionTitle>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px', marginTop: '10px' }}>
{Object.entries(byCategory).slice(0, 4).map(([cat, items]) => (
<div key={cat}>
<p style={{ fontSize: '8px', fontWeight: 700, color: COLORS.indigo, textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: '4px', borderBottom: `1px solid ${COLORS.border}`, paddingBottom: '3px' }}>
{cat}
</p>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '8px' }}>
<Page kicker="18" section={de ? 'ANHANG · ANNAHMEN' : 'APPENDIX · ASSUMPTIONS'} title={de ? 'Treibervariablen des Finanzplans.' : 'Financial plan driver variables.'} subtitle={de ? 'Die Annahmen hinter dem Base-Case. Drei Szenarien (Best/Base/Worst) für Sensitivitätsanalyse verfügbar; Base-Case ist absichtlich konservativ angesetzt.' : 'The assumptions behind the base case. Three scenarios (best/base/worst) available for sensitivity analysis; base case is deliberately conservative.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
{/* Visual category cards: each category as a violet-bordered panel with
its assumptions laid out as a clean two-col list. */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gridAutoRows: 'min-content', gap: '5mm', flex: 1, minHeight: 0, alignContent: 'start' }}>
{categories.slice(0, 6).map(([cat, items], i) => (
<div key={cat} style={{ border: `1px solid ${COLORS.slate200}`, borderTop: `3px solid ${COLORS.violet600}`, background: '#ffffff', padding: '3.5mm 5mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: '2.5mm' }}>
<span style={{ fontFamily: MONO, fontSize: '7.5pt', fontWeight: 700, color: COLORS.violet700, textTransform: 'uppercase', letterSpacing: '0.18em' }}>{cat}</span>
<span style={{ fontFamily: MONO, fontSize: '6.5pt', color: COLORS.slate400, fontVariantNumeric: 'tabular-nums' }}>{String(i + 1).padStart(2, '0')} · {items.length} {de ? 'Variablen' : 'variables'}</span>
</div>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '8.5pt', fontVariantNumeric: 'tabular-nums' }}>
<tbody>
{items.map(a => (
<tr key={a.key}>
<td style={{ padding: '3px 0', color: COLORS.med, paddingRight: '8px' }}>{de ? a.label_de : a.label_en}</td>
<td style={{ padding: '3px 0', fontWeight: 600, color: COLORS.dark, textAlign: 'right', whiteSpace: 'nowrap' }}>
{items.map((a, j) => (
<tr key={a.key} style={{ borderTop: j > 0 ? `1px solid ${COLORS.slate100}` : 'none' }}>
<td style={{ padding: '1.5mm 0', color: COLORS.slate700, paddingRight: '4mm', lineHeight: 1.35 }}>{de ? a.label_de : a.label_en}</td>
<td style={{ padding: '1.5mm 0', textAlign: 'right', fontWeight: 700, color: COLORS.slate900, whiteSpace: 'nowrap' }}>
{typeof a.value === 'number' ? a.value.toLocaleString('de-DE') : String(a.value)}
{a.unit && <span style={{ color: COLORS.light, marginLeft: '2px', fontWeight: 400 }}>{a.unit}</span>}
{a.unit && <span style={{ fontFamily: MONO, color: COLORS.slate500, marginLeft: '1.5mm', fontWeight: 400, fontSize: '7pt' }}>{a.unit}</span>}
</td>
</tr>
))}
@@ -157,121 +248,181 @@ export function PrintAssumptionsPage({ assumptions, lang, pageNum, totalPages, v
</div>
))}
</div>
</PrintPage>
<div style={{ marginTop: '5mm', flexShrink: 0 }}>
<Callout tone="accent" label={de ? 'Sensitivität · Drei Szenarien' : 'Sensitivity · Three Scenarios'}>
{de
? 'Best, Base und Worst Case variieren die kritischen Treiber (Wachstumsrate, Churn, ARPU, CAC). Sensitivitäts-Tornado verfügbar im Live-Modell. Der Base-Case ist absichtlich konservativ — Worst Case noch 8 Monate Runway, Best Case Break-Even 6 Monate früher.'
: 'Best, base and worst case vary the critical drivers (growth rate, churn, ARPU, CAC). Sensitivity tornado available in the live model. The base case is deliberately conservative — worst case still gives 8 months runway, best case breaks even 6 months earlier.'}
</Callout>
</div>
</Page>
)
}
/* ===== P&L DETAIL — now in standard PDF ===== */
export function PrintFinancialsPage({ annualRows, lang, pageNum, totalPages, versionName }: SlideBase & { annualRows: AnnualPLRow[] }) {
const de = lang === 'de'
const breakEvenYear = annualRows.find(r => r.ebitda_eur > 0)?.year
if (annualRows.length === 0) {
return (
<Page kicker="21" section={de ? 'ANHANG · P&L DETAIL' : 'APPENDIX · P&L DETAIL'} title={de ? 'P&L Detail nicht verfügbar' : 'P&L detail unavailable'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<p style={{ fontSize: '10pt', color: COLORS.slate600 }}>{de ? 'Keine Finanzdaten vorhanden. Bitte Base-Case-Szenario auswählen.' : 'No financial data available. Please select base-case scenario.'}</p>
</Page>
)
}
return (
<Page kicker="21" section={de ? 'ANHANG · P&L DETAIL' : 'APPENDIX · P&L DETAIL'} title={de ? 'Annualisierte Gewinn- und Verlust-Rechnung.' : 'Annualized profit & loss.'} subtitle={de ? 'Konsolidierte Jahreswerte 20262030 in EUR. Klammern () zeigen Aufwendungen. EBITDA in grün ab Break-Even.' : 'Consolidated annual values 20262030 in EUR. Parentheses () mark costs. EBITDA in green from break-even on.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<DataTable
cols={[
{ header: de ? 'Jahr' : 'Year', width: '8%' },
{ header: de ? 'Umsatz' : 'Revenue', numeric: true, width: '13%' },
{ header: de ? 'Rohertrag' : 'Gross Profit', numeric: true, width: '13%' },
{ header: 'Personal', numeric: true, width: '12%' },
{ header: 'Marketing', numeric: true, width: '12%' },
{ header: 'Infra', numeric: true, width: '10%' },
{ header: 'EBITDA', numeric: true, width: '12%' },
{ header: de ? 'Kunden' : 'Customers', numeric: true, width: '10%' },
{ header: 'FTE', numeric: true, width: '10%' },
]}
rows={annualRows.map(r => [
<strong key="y">{r.year}</strong>,
<span style={{ color: COLORS.slate900, fontWeight: 700 }} key="rev">{fmtEur(r.revenue_eur)}</span>,
fmtEur(r.gross_profit_eur),
`(${fmtEur(r.personnel_eur)})`,
`(${fmtEur(r.marketing_eur)})`,
`(${fmtEur(r.infra_eur)})`,
<strong key="eb" style={{ color: r.ebitda_eur >= 0 ? COLORS.emerald700 : COLORS.red700 }}>{fmtEur(r.ebitda_eur)}</strong>,
r.total_customers.toString(),
r.employees_count.toString(),
])}
/>
<div style={{ marginTop: '5mm', flexShrink: 0 }}>
<Callout tone={breakEvenYear ? 'positive' : 'caution'} label={de ? 'Break-Even-Indikator' : 'Break-even indicator'}>
{de
? `Erstes Jahr mit positivem EBITDA: ${breakEvenYear ?? 'außerhalb der Planungsperiode'}. In Klammern () = Kosten. Planzahlen, kein Versprechen.`
: `First year with positive EBITDA: ${breakEvenYear ?? 'outside planning period'}. Parentheses () = costs. Projections, no guarantee.`}
</Callout>
</div>
</Page>
)
}
/* ===== CAP TABLE ===== */
const CAP_TABLE_DATA = [
{ name: 'Benjamin Bönisch (CEO)', pct: 37.3, color: '#6366f1' },
{ name: 'Sharang Parnerkar (CTO)', pct: 37.3, color: '#8b5cf6' },
{ name: 'Pre-Seed Investor', pct: 20.0, color: '#f59e0b' },
{ name: 'Benjamin Bönisch (CEO)', pct: 37.3, color: '#4f46e5' },
{ name: 'Sharang Parnerkar (CTO)', pct: 37.3, color: '#6366f1' },
{ name: 'Pre-Seed Investor', pct: 20.0, color: '#d97706' },
{ name: 'ESOP Pool', pct: 5.4, color: '#94a3b8' },
]
export function PrintCapTablePage({ lang, pageNum, totalPages, versionName }: SlideBase) {
const de = lang === 'de'
return (
<PrintPage title={de ? 'Cap Table' : 'Cap Table'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<SectionTitle subtitle={de ? '4 Mio. EUR Pre-Money · 1 Mio. EUR Pre-Seed · Gründung Aug 2026' : 'EUR 4M pre-money · EUR 1M pre-seed · Founding Aug 2026'}>
{de ? 'Investition & Anteilsverteilung' : 'Investment & Share Distribution'}
</SectionTitle>
<div style={{ display: 'flex', gap: '24px', marginTop: '12px', alignItems: 'flex-start' }}>
{/* Stacked bar */}
<div style={{ flex: 1 }}>
<p style={{ fontSize: '9px', fontWeight: 700, color: COLORS.dark, marginBottom: '8px' }}>
{de ? 'Anteilsverteilung nach Pre-Seed' : 'Share Distribution Post Pre-Seed'}
</p>
<div style={{ display: 'flex', height: '20px', borderRadius: '4px', overflow: 'hidden', marginBottom: '10px' }}>
{CAP_TABLE_DATA.map(d => (
<div key={d.name} style={{ width: `${d.pct}%`, backgroundColor: d.color, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
))}
</div>
{CAP_TABLE_DATA.map(d => (
<div key={d.name} style={{ display: 'flex', alignItems: 'center', gap: '6px', marginBottom: '5px' }}>
<div style={{ width: '10px', height: '10px', borderRadius: '2px', backgroundColor: d.color, flexShrink: 0, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
<span style={{ fontSize: '9px', color: COLORS.med, flex: 1 }}>{d.name}</span>
<span style={{ fontSize: '10px', fontWeight: 700, color: COLORS.dark }}>{d.pct}%</span>
<Page kicker="C" section={de ? 'CAP TABLE (FINANCIAL-ONLY)' : 'CAP TABLE (FINANCIAL ONLY)'} title={de ? '€4M Pre-Money · €1M Pre-Seed · Gründung Aug 2026.' : '€4M pre-money · €1M pre-seed · founding Aug 2026.'} subtitle={de ? 'Anteilsverteilung nach Pre-Seed-Closing. Beide Gründer mit 4-Jahres-Vesting + 1-Jahr-Cliff.' : 'Share distribution post pre-seed closing. Both founders on 4-year vesting + 1-year cliff.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<div style={{ display: 'grid', gridTemplateColumns: '1.5fr 1fr', gap: '8mm', flex: 1, minHeight: 0 }}>
<div>
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '3mm' }}>{de ? 'Anteilsverteilung Post Pre-Seed' : 'Share distribution post pre-seed'}</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '8mm', marginBottom: '5mm' }}>
<DonutChart size={50} thickness={11} segments={CAP_TABLE_DATA.map(d => ({ label: d.name, pct: d.pct, color: d.color }))} />
<div style={{ flex: 1 }}>
{CAP_TABLE_DATA.map(d => (
<div key={d.name} style={{ display: 'flex', alignItems: 'center', gap: '3mm', padding: '2mm 0', borderBottom: `1px solid ${COLORS.slate100}` }}>
<div style={{ width: '4mm', height: '4mm', background: d.color, flexShrink: 0, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
<span style={{ fontSize: '9.5pt', color: COLORS.slate800, flex: 1, fontWeight: 500 }}>{d.name}</span>
<span style={{ fontSize: '14pt', fontWeight: 800, color: COLORS.slate900, fontVariantNumeric: 'tabular-nums' }}>{d.pct}%</span>
</div>
))}
</div>
))}
</div>
</div>
{/* Deal terms */}
<div style={{ flexShrink: 0, minWidth: '180px' }}>
<p style={{ fontSize: '9px', fontWeight: 700, color: COLORS.dark, marginBottom: '8px' }}>
{de ? 'Konditionen' : 'Deal Terms'}
</p>
<PrintTable
headers={['', '']}
<div>
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '3mm' }}>{de ? 'Deal Terms' : 'Deal terms'}</div>
<DataTable
cols={[{ header: '', width: '50%' }, { header: '', numeric: true }]}
rows={[
[de ? 'Pre-Money' : 'Pre-Money', '4.000.000 EUR'],
[de ? 'Investment' : 'Investment', '1.000.000 EUR'],
[de ? 'Post-Money' : 'Post-Money', '5.000.000 EUR'],
[de ? 'Investor-Anteil' : 'Investor Share', '20 %'],
['ESOP Pool', '5,4 %'],
['INVEST-Zuschuss', '20 %'],
[de ? 'Pre-Money' : 'Pre-money', '4.000.000'],
[de ? 'Investment' : 'Investment', '1.000.000'],
[de ? 'Post-Money' : 'Post-money', '5.000.000'],
[de ? 'Investor-Anteil' : 'Investor share', '20%'],
['ESOP Pool', '5,4%'],
[de ? 'Vesting' : 'Vesting', de ? '4 J / 1 J Cliff' : '4 yr / 1 yr cliff'],
['INVEST-Zuschuss', '20% (BMWi)'],
[de ? 'Liquidationspräf.' : 'Liquidation pref.', '1x non-part.'],
]}
highlightFirstCol
/>
<div style={{ marginTop: '4mm' }}>
<Callout tone="accent" label={de ? 'INVEST-Zuschuss' : 'INVEST grant'}>
{de
? 'BMWi-Förderung: 20% des Investments zurück an den Investor (bis €100k / Jahr). Effektive Kapitalkosten sinken entsprechend.'
: 'BMWi grant: 20% of investment refunded to investor (up to €100k / year). Effective capital cost reduced accordingly.'}
</Callout>
</div>
</div>
</div>
</PrintPage>
</Page>
)
}
const DISCLAIMER_DE = {
heading: 'Rechtlicher Hinweis',
/* ===== DISCLAIMER ===== */
const DC_DE = {
h1: 'Haftungsausschluss',
p1: '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.',
p1: 'Dieses Dokument wird vorgelegt von Benjamin Bönisch, 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.',
p2: '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 im Sinne des VermAnlG oder der EU-Prospektverordnung.',
p3: 'Dieses Dokument enthält zukunftsgerichtete Aussagen, die auf gegenwärtigen Erwartungen und Annahmen beruhen. Sämtliche Finanzangaben sind Planzahlen und stellen keine Garantie für künftige Ergebnisse dar.',
p4: 'Eine Beteiligung an einem jungen Unternehmen ist mit erheblichen Risiken verbunden, einschließlich des Risikos eines Totalverlusts des eingesetzten Kapitals.',
h2: 'Vertraulichkeit',
p5: '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:',
pa: '(a) Geheimhaltung Inhalt vertraulich behandeln und nicht an Dritte weitergeben.',
pb: '(b) Zweckbindung Ausschließlich zur Bewertung einer möglichen Beteiligung verwenden.',
pc: '(c) Geltungsdauer Diese Vertraulichkeitsverpflichtung gilt für drei (3) Jahre ab Übermittlung. Gerichtsstand ist Konstanz, Deutschland.',
pa: '(a) Geheimhaltung, Inhalt vertraulich behandeln und nicht an Dritte weitergeben.',
pb: '(b) Zweckbindung, Ausschließlich zur Bewertung einer möglichen Beteiligung verwenden.',
pc: '(c) Geltungsdauer, Diese Vertraulichkeitsverpflichtung gilt für drei (3) Jahre ab Übermittlung. Gerichtsstand ist Konstanz, Deutschland.',
footer: 'Stand: April 2026 · Dieser Hinweis ersetzt keine Rechtsberatung.',
}
const DISCLAIMER_EN = {
heading: 'Legal Notice',
const DC_EN = {
h1: 'Disclaimer',
p1: '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.',
p1: 'This document is presented by Benjamin Bönisch, 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.',
p2: 'This document constitutes neither an offer to sell nor a solicitation of an offer to acquire securities. It is not a securities prospectus within the meaning of VermAnlG or the EU Prospectus Regulation.',
p3: 'This document contains forward-looking statements based on current expectations. All financial figures are projections and do not constitute a guarantee of future results.',
p4: 'An investment in a young company involves significant risks, including the risk of total loss of invested capital.',
h2: 'Confidentiality',
p5: 'This document is confidential and prepared exclusively for the personally invited recipient. By accessing, the recipient agrees to:',
pa: '(a) Confidentiality Treat contents confidentially and not disclose to third parties.',
pb: '(b) Purpose limitation Use only for evaluating a possible participation.',
pc: '(c) Duration This obligation applies for three (3) years from transmission. Place of jurisdiction is Konstanz, Germany.',
pa: '(a) Confidentiality, Treat contents confidentially and not disclose to third parties.',
pb: '(b) Purpose limitation, Use only for evaluating a possible participation.',
pc: '(c) Duration, This obligation applies for three (3) years from transmission. Place of jurisdiction is Konstanz, Germany.',
footer: 'As of: April 2026 · This notice does not replace legal advice.',
}
export function PrintDisclaimerPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
const d = lang === 'de' ? DISCLAIMER_DE : DISCLAIMER_EN
const sectionStyle = { padding: '10px 12px', border: `1px solid ${COLORS.border}`, borderRadius: '6px', marginBottom: '8px' }
const pStyle = { fontSize: '8px', color: COLORS.med, lineHeight: 1.55, margin: '4px 0 0' }
const hStyle = { fontSize: '9px', fontWeight: 700, color: COLORS.indigo, margin: 0, textTransform: 'uppercase' as const, letterSpacing: '0.05em' }
const d = lang === 'de' ? DC_DE : DC_EN
const sectionStyle: React.CSSProperties = { padding: '4mm 5mm', border: `1px solid ${COLORS.slate200}`, marginBottom: '3mm' }
const pStyle: React.CSSProperties = { fontSize: '8.5pt', color: COLORS.slate700, lineHeight: 1.55, margin: '2mm 0 0' }
const hStyle: React.CSSProperties = { fontSize: '9pt', fontWeight: 700, color: COLORS.indigo600, margin: 0, textTransform: 'uppercase', letterSpacing: '0.08em' }
return (
<PrintPage title={d.heading} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<SectionTitle>{d.heading}</SectionTitle>
<div style={{ marginTop: '8px' }}>
<div style={sectionStyle}>
<p style={hStyle}>{d.h1}</p>
<p style={pStyle}>{d.p1}</p>
<p style={pStyle}>{d.p2}</p>
<p style={pStyle}>{d.p3}</p>
<p style={pStyle}>{d.p4}</p>
</div>
<div style={sectionStyle}>
<p style={hStyle}>{d.h2}</p>
<p style={pStyle}>{d.p5}</p>
<p style={pStyle}>{d.pa}</p>
<p style={pStyle}>{d.pb}</p>
<p style={pStyle}>{d.pc}</p>
</div>
<Page kicker="25" section={lang === 'de' ? 'RECHTLICHER HINWEIS' : 'LEGAL NOTICE'} title={d.h1} subtitle={lang === 'de' ? 'Vertraulich · Nur für den eingeladenen Empfänger · Kein Wertpapierprospekt' : 'Confidential · For invited recipient only · Not a securities prospectus'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<div style={sectionStyle}>
<p style={hStyle}>{d.h1}</p>
<p style={pStyle}>{d.p1}</p>
<p style={pStyle}>{d.p2}</p>
<p style={pStyle}>{d.p3}</p>
<p style={pStyle}>{d.p4}</p>
</div>
<p style={{ fontSize: '8px', color: COLORS.light, textAlign: 'center', marginTop: '6px' }}>{d.footer}</p>
</PrintPage>
<div style={sectionStyle}>
<p style={hStyle}>{d.h2}</p>
<p style={pStyle}>{d.p5}</p>
<p style={pStyle}>{d.pa}</p>
<p style={pStyle}>{d.pb}</p>
<p style={pStyle}>{d.pc}</p>
</div>
<p style={{ fontSize: '8pt', color: COLORS.slate500, textAlign: 'center', marginTop: '4mm', fontStyle: 'italic' }}>{d.footer}</p>
</Page>
)
}
@@ -0,0 +1,515 @@
import { Language, PitchCompany, PitchFunding, PitchMarket } from '@/lib/types'
import { Page, KpiRow, TwoCol, ThreeCol, FourCol, Panel, Bullets, Callout, COLORS, Divider } from './PrintLayout'
interface SlideBase { lang: Language; pageNum: number; totalPages: number; versionName: string }
/* ===== COVER ===== */
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.')
const amountLabel = amount >= 1_000_000
? '€' + (amount / 1_000_000).toFixed(1).replace(/\.0$/, '') + 'M'
: '€' + Math.round(amount / 1_000) + 'k'
const isConvertible = (instrument || '').toLowerCase().includes('wandeldarlehen') ||
(instrument || '').toLowerCase().includes('convertible') ||
(instrument || '').toLowerCase().includes('safe')
const coverTerms: [string, string][] = isConvertible
? [
[de ? 'Funding' : 'Funding', amountLabel],
[de ? 'Instrument' : 'Instrument', instrument],
[de ? 'Laufzeit' : 'Maturity', de ? '24 Mo.' : '24 mo'],
]
: [
[de ? 'Funding' : 'Funding', amountLabel],
[de ? 'Pre-Money' : 'Pre-money', '€4.0M'],
[de ? 'Instrument' : 'Instrument', instrument],
]
const MONO_FONT = "'JetBrains Mono', ui-monospace, Menlo, Consolas, monospace"
return (
<div className="print-page-break">
<div className="print-page print-page-bg" style={{ width: '297mm', height: '210mm', color: COLORS.slate900, fontFamily: "'Inter', system-ui, sans-serif", boxSizing: 'border-box', margin: '0 auto 24px', boxShadow: '0 4px 24px rgba(59,26,122,0.10)', overflow: 'hidden', padding: 0 }}>
<div style={{ width: '100%', height: '100%', display: 'grid', gridTemplateColumns: '95mm 1fr' }}>
{/* LEFT VIOLET BLOCK */}
<div style={{
background: `linear-gradient(180deg, ${COLORS.violet700} 0%, ${COLORS.violet600} 60%, ${COLORS.violet700} 100%)`,
color: '#ffffff',
padding: '16mm 12mm',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
WebkitPrintColorAdjust: 'exact',
printColorAdjust: 'exact',
}}>
<div>
<div style={{ fontFamily: MONO_FONT, fontSize: '7.5pt', fontWeight: 700, letterSpacing: '0.22em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.78)' }}>
{de ? 'Investor Brief' : 'Investor Brief'}
</div>
<div style={{ marginTop: '6mm', height: '1px', background: 'rgba(255,255,255,0.4)', width: '32mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
<div style={{ marginTop: '6mm', fontSize: '11pt', color: '#ffffff', lineHeight: 1.5, fontWeight: 500 }}>
{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.'}
</div>
</div>
{/* Mid stats */}
<div style={{ paddingTop: '8mm', borderTop: '1px solid rgba(255,255,255,0.3)' }}>
<div style={{ fontFamily: MONO_FONT, fontSize: '7.5pt', fontWeight: 700, letterSpacing: '0.18em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.78)', marginBottom: '4mm' }}>{de ? 'Auf einen Blick' : 'At a glance'}</div>
<div style={{ fontFamily: MONO_FONT, fontSize: '8.5pt', color: '#ffffff', lineHeight: 1.85, fontWeight: 500 }}>
{de ? '25 000+ atomare Prüfaspekte' : '25 000+ atomic audit aspects'}<br />
{de ? '380+ Regularien · 10 Branchen' : '380+ regulations · 10 industries'}<br />
{de ? '500K+ Lines of Code · 45 Container' : '500K+ lines of code · 45 containers'}<br />
{de ? '100% EU-Hosting · BSI Cloud DE' : '100% EU hosting · BSI cloud DE'}
</div>
</div>
{/* Footer */}
<div>
<div style={{ fontFamily: MONO_FONT, fontSize: '7pt', letterSpacing: '0.2em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.65)', fontWeight: 700 }}>{versionName}</div>
<div style={{ fontFamily: MONO_FONT, marginTop: '2mm', fontSize: '7pt', letterSpacing: '0.2em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.65)', fontWeight: 700 }}>{de ? 'Vertraulich · Nur Investoren' : 'Confidential · Investors only'}</div>
</div>
</div>
{/* RIGHT (violet-tinted dotted bg from .print-page-bg) PANE */}
<div className="print-page-bg" style={{ padding: '18mm 16mm', display: 'flex', flexDirection: 'column', justifyContent: 'space-between', minWidth: 0 }}>
<div>
<div style={{ fontFamily: MONO_FONT, fontSize: '9pt', fontWeight: 700, color: COLORS.violet600, textTransform: 'uppercase', letterSpacing: '0.22em' }}>
{instrument} &middot; Q4 2026
</div>
<h1 style={{ fontSize: '60pt', fontWeight: 800, color: COLORS.slate900, lineHeight: 0.95, letterSpacing: '-0.03em', margin: '8mm 0 4mm' }}>
{company?.name || 'BreakPilot'}<span style={{ color: COLORS.indigo600 }}>.</span>
</h1>
<div style={{ height: '2px', width: '40mm', background: COLORS.indigo600, marginBottom: '6mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
<p style={{ fontSize: '15pt', fontWeight: 500, color: COLORS.slate700, lineHeight: 1.3, maxWidth: '170mm', margin: 0, letterSpacing: '-0.008em' }}>
{tagline}
</p>
</div>
{/* Key terms */}
<div>
<div style={{ fontFamily: MONO_FONT, fontSize: '7.5pt', fontWeight: 700, color: COLORS.violet600, textTransform: 'uppercase', letterSpacing: '0.2em', marginBottom: '3mm', paddingBottom: '2mm', borderBottom: `1px solid ${COLORS.slate200}` }}>
{de ? 'Key Terms' : 'Key terms'}
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '6mm' }}>
{coverTerms.map(([label, val]) => (
<div key={label}>
<div style={{ fontFamily: MONO_FONT, fontSize: '7pt', color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.18em', fontWeight: 700 }}>{label}</div>
<div style={{ fontSize: '17pt', fontWeight: 800, color: COLORS.slate900, marginTop: '2mm', fontVariantNumeric: 'tabular-nums', letterSpacing: '-0.015em', lineHeight: 1.05 }}>{val}</div>
</div>
))}
</div>
<div style={{ marginTop: '5mm', fontSize: '8.5pt', color: COLORS.slate600, lineHeight: 1.5 }}>
{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.'}
</div>
</div>
</div>
</div>
</div>
</div>
)
}
/* ===== EXECUTIVE SUMMARY, PAGE 1 ===== */
export function PrintExecSummaryPage1({ market, lang, pageNum, totalPages, versionName }: SlideBase & { market: PitchMarket[] }) {
const de = lang === 'de'
const tam = market.find(m => m.market_segment === 'TAM')
const sam = market.find(m => m.market_segment === 'SAM')
const som = market.find(m => m.market_segment === 'SOM')
const fmt = (v?: number) => v ? (v >= 1e9 ? `${(v / 1e9).toFixed(1).replace('.', ',')} Mrd.` : `${(v / 1e6).toFixed(0)} Mio.`) : '—'
return (
<Page kicker="01" section={de ? 'EXECUTIVE SUMMARY' : 'EXECUTIVE SUMMARY'} title={de ? 'BreakPilot COMPL/AI/' : 'BreakPilot COMPL/AI/'} subtitle={de ? 'DSGVO-konforme KI-Plattform, kontinuierliches Sicherheitsscanning und intelligente Compliance-Automatisierung. 25.000+ atomare Prüfaspekte, 380+ Regularien, EU-souverän gehostet.' : 'GDPR-compliant AI platform, continuous security scanning and intelligent compliance automation. 25,000+ atomic audit aspects, 380+ regulations, EU-sovereign hosted.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<KpiRow items={[
{ n: '25k+', label: de ? 'Prüfaspekte' : 'Audit aspects', tone: 'accent' },
{ n: '380+', label: de ? 'Regularien' : 'Regulations' },
{ n: '10', label: de ? 'Branchen' : 'Industries' },
{ n: '500K+', label: de ? 'Lines of Code' : 'Lines of code' },
{ n: '80%', label: de ? 'Zeit gespart' : 'Time saved', tone: 'positive' },
{ n: '10×', label: de ? 'günstiger als Pentest' : 'cheaper than pentest', tone: 'positive' },
]} />
<div style={{ marginTop: '5mm', flex: 1, minHeight: 0 }}>
<TwoCol
ratio="1:1"
gap="8mm"
left={
<Panel label={de ? 'Das Problem' : 'The Problem'} tone="negative">
<p style={{ marginTop: 0, marginBottom: '3mm', fontStyle: 'italic', color: COLORS.slate600 }}>
{de ? 'Unternehmen stehen vor einer unlösbaren Entscheidung:' : 'Companies face an impossible decision:'}
</p>
<Bullets dense tone="negative" items={de ? [
'Ohne KI verlieren sie ihre Wettbewerbsfähigkeit',
'Mit US-KI riskieren sie die Kontrolle über sensible Daten',
'AI Act, CRA, NIS2 zwingen 30.000+ Unternehmen in komplexe Compliance',
'EU-Regulierung unterscheidet nicht zwischen klein und groß',
'Pentests + Audits: 15-40k EUR pro Prüfung, nur einmal jährlich',
'Ergebnis: Stillstand in einer Phase, in der Geschwindigkeit zählt',
] : [
'Without AI they lose their competitiveness',
'With US AI they risk losing control over sensitive data',
'AI Act, CRA, NIS2 force 30,000+ companies into complex compliance',
'EU regulation does not differentiate between small and large',
'Pentests + audits: EUR 15-40k per check, only once yearly',
'Result: standstill in a phase where speed is decisive',
]} />
</Panel>
}
right={
<Panel label={de ? 'Unsere Lösung' : 'Our Solution'} tone="positive">
<p style={{ marginTop: 0, marginBottom: '3mm', fontStyle: 'italic', color: COLORS.slate600 }}>
{de ? 'BreakPilot macht Compliance und Security kontinuierlich, nicht punktuell.' : 'BreakPilot makes compliance and security continuous, not periodic.'}
</p>
<Bullets dense tone="positive" items={de ? [
'Jede Code-Änderung automatisch geprüft (SAST, DAST, SBOM, Pentest)',
'VVT, TOMs, DSFA, Löschfristen in Echtzeit generiert',
'CE-Software-Risikobeurteilung schon in der Entwicklung',
'Abweichungen End-to-End: Tickets, Nachweise, Eskalation an GF',
'Compliance GPT für komplexe regulatorische Fragen',
'Gehostet in EU-Infrastruktur (DE/FR), audit-ready zu jeder Zeit',
] : [
'Every code change automatically checked (SAST, DAST, SBOM, pentest)',
'RoPA, TOMs, DPIA, retention policies in real time',
'CE software risk assessment from code, during development',
'Deviations end-to-end: tickets, evidence, escalation to mgmt',
'Compliance GPT for complex regulatory questions',
'Hosted on EU infra (DE/FR), audit-ready at any time',
]} />
</Panel>
}
/>
</div>
<div style={{ marginTop: '4mm', flexShrink: 0 }}>
<Callout tone="accent" label={de ? 'Unser MOAT' : 'Our MOAT'}>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '5mm', marginTop: '1mm' }}>
{[
{ t: 'Traceability', d: de ? 'Gesetz → Control → Code' : 'Law → Control → Code' },
{ t: 'Continuous Engine', d: de ? 'Echtzeit bei jeder Änderung' : 'Real-time on every change' },
{ t: 'Compliance Optimizer', d: de ? 'Max. KI-Nutzung im legalen Rahmen' : 'Max AI use within regulations' },
{ t: 'EU-Trust Stack', d: de ? '100% EU, kein US-SaaS' : '100% EU, no US SaaS' },
].map((m, i) => (
<div key={i}>
<div style={{ fontSize: '9pt', fontWeight: 700, color: COLORS.indigo700 }}>{m.t}</div>
<div style={{ fontSize: '8pt', color: COLORS.slate600, marginTop: '1mm' }}>{m.d}</div>
</div>
))}
</div>
</Callout>
</div>
{/* Tiny market footer */}
<div style={{ flexShrink: 0, marginTop: '3mm', display: 'flex', gap: '6mm', fontSize: '8pt', color: COLORS.slate500 }}>
<span><strong style={{ color: COLORS.slate800 }}>TAM:</strong> {fmt(tam?.value_eur)}</span>
<span><strong style={{ color: COLORS.slate800 }}>SAM:</strong> {fmt(sam?.value_eur)}</span>
<span><strong style={{ color: COLORS.slate800 }}>SOM:</strong> {fmt(som?.value_eur)}</span>
<span style={{ flex: 1 }} />
<span style={{ color: COLORS.slate400 }}>{de ? 'Fortsetzung auf Folgeseite →' : 'Continued on next page →'}</span>
</div>
</Page>
)
}
/* ===== EXECUTIVE SUMMARY, PAGE 2 ===== */
const MODULES_DE = [
{ name: 'Code Security', desc: 'SAST, DAST, SBOM, Pentesting' },
{ name: 'CE-SW-Risiko', desc: 'CE-Kennzeichnung Maschinen' },
{ name: 'Compliance Docs', desc: 'VVT, DSFA, TOMs, Löschfristen' },
{ name: 'Audit Manager', desc: 'Abweichungen, Nachweise, Eskalation' },
{ name: 'DSR / Betroffene', desc: 'Auskunft, Löschung, Berichtigung' },
{ name: 'Consent', desc: 'Einwilligungs-Management' },
{ name: 'Notfallpläne', desc: 'Vorfälle, Meldung, Mitigation' },
{ name: 'Compliance LLM', desc: 'GPT (Text + Audio), EU-gehostet' },
{ name: 'Tender Matching', desc: 'RFQ-Antworten gegen Codebase' },
{ name: 'Academy', desc: 'Online-Schulungen GF + MA' },
{ name: 'Compliance Optimizer', desc: 'Max. KI-Nutzung im Rahmen' },
{ name: 'Kommunikation', desc: 'Chat + Video + KI-Support' },
]
const MODULES_EN = [
{ name: 'Code Security', desc: 'SAST, DAST, SBOM, pentesting' },
{ name: 'CE SW Risk', desc: 'CE marking for machinery' },
{ name: 'Compliance Docs', desc: 'RoPA, DPIA, TOMs, retention' },
{ name: 'Audit Manager', desc: 'Deviations, evidence, escalation' },
{ name: 'DSR / Data Subj.', desc: 'Access, erasure, rectification' },
{ name: 'Consent', desc: 'Consent management' },
{ name: 'Incident Resp.', desc: 'Breaches, reporting, mitigation' },
{ name: 'Compliance LLM', desc: 'GPT (text + audio), EU-hosted' },
{ name: 'Tender Matching', desc: 'RFQ answers against codebase' },
{ name: 'Academy', desc: 'Online training for mgmt + staff' },
{ name: 'Compliance Optimizer', desc: 'Max AI usage within limits' },
{ name: 'Communication', desc: 'Chat + video + AI support' },
]
export function PrintExecSummaryPage2({ lang, pageNum, totalPages, versionName }: SlideBase) {
const de = lang === 'de'
const modules = de ? MODULES_DE : MODULES_EN
return (
<Page kicker="01" section={de ? 'EXECUTIVE SUMMARY · DETAIL' : 'EXECUTIVE SUMMARY · DETAIL'} title={de ? 'Modularer Baukasten und Geschäftsverlauf' : 'Modular Toolkit and Trajectory'} subtitle={de ? '12 Module, 3-Phasen-GTM, 5-Jahres-Trajektorie. Kunden zahlen ~50k €/Jahr und sparen 55k+ €.' : '12 modules, 3-phase GTM, 5-year trajectory. Customers pay ~€50k/yr, save €55k+.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
{/* MODULES 4x3 */}
<div style={{ marginBottom: '4mm' }}>
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '2mm' }}>{de ? '12 Module, Kunden wählen einzeln oder alles' : '12 modules, pick individually or take the bundle'}</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '3mm' }}>
{modules.map((m, i) => (
<div key={i} style={{ border: `1px solid ${COLORS.slate200}`, padding: '2.5mm 3mm', minHeight: '12mm' }}>
<div style={{ fontSize: '9pt', fontWeight: 700, color: COLORS.slate900 }}>{m.name}</div>
<div style={{ fontSize: '7.5pt', color: COLORS.slate600, marginTop: '1mm', lineHeight: 1.35 }}>{m.desc}</div>
</div>
))}
</div>
</div>
<Divider />
{/* GTM PHASES + ARR + PRICING + SAVINGS */}
<div style={{ display: 'grid', gridTemplateColumns: '1.4fr 1fr 1fr', gap: '6mm', flex: 1, minHeight: 0 }}>
{/* GTM Phases */}
<div>
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '2mm' }}>{de ? 'Go-to-Market, 3 Phasen' : 'Go-to-Market, 3 Phases'}</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '2.5mm' }}>
{[
{ t: de ? 'Phase 1 · Pilot (Jul/Aug 2026)' : 'Phase 1 · Pilot (Jul/Aug 2026)', tone: COLORS.indigo600, items: de ? ['GmbH-Gründung', 'Direktvertrieb Maschinenbau', 'White-Glove-Onboarding', 'Erste Referenzkunden'] : ['GmbH incorporation', 'Direct sales to manufacturing', 'White-glove onboarding', 'First reference customers'] },
{ t: de ? 'Phase 2 · Skalierung (2027)' : 'Phase 2 · Scale (2027)', tone: COLORS.indigo600, items: de ? ['Channel über IT-Systemhäuser', 'IHK-Kooperationen, Messen', 'Content-Marketing + Webinare', '50200 Kunden in reg. Branchen'] : ['Channel via IT integrators', 'Chamber of Commerce, fairs', 'Content marketing + webinars', '50200 customers in reg. sectors'] },
{ t: de ? 'Phase 3 · Expansion (2028+)' : 'Phase 3 · Expansion (2028+)', tone: COLORS.emerald600, items: de ? ['Enterprise (50500 MA)', 'EU-Expansion (AT, CH, Benelux)', 'Distributor-Partnerschaften', 'Break-Even Q3 / 2029'] : ['Enterprise (50500 emp.)', 'EU expansion (AT, CH, Benelux)', 'Distributor partnerships', 'Break-even Q3 / 2029'] },
].map((p, i) => (
<div key={i} style={{ borderLeft: `2px solid ${p.tone}`, paddingLeft: '3mm' }}>
<div style={{ fontSize: '9pt', fontWeight: 700, color: COLORS.slate900, marginBottom: '1mm' }}>{p.t}</div>
<div style={{ fontSize: '7.5pt', color: COLORS.slate600, lineHeight: 1.45 }}>{p.items.join(' · ')}</div>
</div>
))}
</div>
</div>
{/* Pricing */}
<div>
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '2mm' }}>Pricing</div>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '8pt', fontVariantNumeric: 'tabular-nums' }}>
<tbody>
{[
[de ? 'Starter (<10 MA)' : 'Starter (<10 emp.)', de ? '3.600 €/J' : '€3,600/yr'],
[de ? 'Professional (10250)' : 'Professional (10250)', de ? '1540k €/J' : '€1540k/yr'],
[de ? 'Enterprise (250+)' : 'Enterprise (250+)', de ? 'ab 50k €/J' : 'from €50k/yr'],
].map((r, i) => (
<tr key={i} style={{ borderBottom: `1px solid ${COLORS.slate100}` }}>
<td style={{ padding: '2mm 0', color: COLORS.slate700 }}>{r[0]}</td>
<td style={{ padding: '2mm 0', textAlign: 'right', fontWeight: 700, color: i === 1 ? COLORS.indigo600 : COLORS.slate900 }}>{r[1]}</td>
</tr>
))}
</tbody>
</table>
<div style={{ marginTop: '3mm', fontSize: '7.5pt', color: COLORS.slate500, lineHeight: 1.4 }}>
{de ? 'Mitarbeiterbasiert. SaaS-Subscription. BSI-Cloud DE. Modular erweiterbar.' : 'Employee-based. SaaS subscription. BSI cloud DE. Modular.'}
</div>
</div>
{/* Customer Savings */}
<div>
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '2mm' }}>{de ? 'Kundenersparnis (KMU/Jahr)' : 'Customer Savings (SME/yr)'}</div>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '8pt', fontVariantNumeric: 'tabular-nums' }}>
<tbody>
{[
['Pentests', '13k', false],
[de ? 'CE-Risiko' : 'CE risk', '9k', false],
[de ? 'Compliance-Zeit' : 'Compliance time', '15k', false],
[de ? 'Audit-Vorber.' : 'Audit prep', '9k', false],
[de ? 'Sonstiges' : 'Other', '9k', false],
[de ? 'Summe / KMU / Jahr' : 'Total / SME / yr', '55k €', true],
].map((r, i) => (
<tr key={i} style={{ borderBottom: `1px solid ${r[2] ? COLORS.emerald600 : COLORS.slate100}` }}>
<td style={{ padding: '2mm 0', color: r[2] ? COLORS.slate900 : COLORS.slate700, fontWeight: r[2] ? 700 : 400 }}>{r[0]}</td>
<td style={{ padding: '2mm 0', textAlign: 'right', fontWeight: 700, color: r[2] ? COLORS.emerald700 : COLORS.slate700 }}>{r[1]}</td>
</tr>
))}
</tbody>
</table>
<div style={{ marginTop: '3mm', fontSize: '7.5pt', color: COLORS.emerald700, fontWeight: 600 }}>
{de ? 'ROI ab Tag 1. Kunden sparen mehr als sie zahlen.' : 'ROI from day 1. Customers save more than they pay.'}
</div>
</div>
</div>
</Page>
)
}
/* ===== PROBLEM ===== */
const DE_PROBLEM_CARDS = [
{ kicker: 'KI-DILEMMA', stat: 'Abgehängt', 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.', cite: 'Bitkom Cloud Monitor 2024 · DIHK Digitalisierungsumfrage 2024',
pulls: [
{ v: '64%', l: 'deutsche Industrie lehnt US-Cloud für sensible Daten ab' },
{ v: '>70%', l: 'Ablehnung im Maschinenbau speziell' },
{ v: '83%', l: 'KMU sehen Datenschutz als Haupthindernis für KI-Einsatz' },
],
},
{ kicker: 'PATRIOT ACT + FISA 702', stat: 'Kein Schutz', 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.', cite: 'EuGH C-311/18 (Schrems II, 2020)',
pulls: [
{ v: '2020', l: 'EuGH erklärt EU-US Privacy Shield für ungültig' },
{ v: '0', l: 'rechtssichere Wege für Cloud-Daten via US-Anbieter' },
{ v: 'CLOUD Act', l: 'gilt extraterritorial für US-Mutterkonzerne' },
],
},
{ kicker: 'REGULIERUNGS-TSUNAMI', 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 Compliance-Kosten, die US- und Asien-Konkurrenten nicht haben.', cite: 'VDMA Compliance-Kosten Maschinenbau 2024',
pulls: [
{ v: '30 000+', l: 'DE-Unternehmen direkt von NIS2 betroffen' },
{ v: '€15-40k', l: 'pro externem Pentest, einmal jährlich' },
{ v: '€10-25k', l: 'pro CE-Software-Risikobeurteilung' },
],
},
]
const EN_PROBLEM_CARDS = [
{ kicker: 'AI DILEMMA', stat: 'Left behind', 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.', cite: 'Bitkom Cloud Monitor 2024 · DIHK 2024',
pulls: [
{ v: '64%', l: 'of German industry refuses US cloud for sensitive data' },
{ v: '>70%', l: 'rejection in manufacturing specifically' },
{ v: '83%', l: 'of SMEs cite data protection as the top AI barrier' },
],
},
{ kicker: 'PATRIOT ACT + FISA 702', stat: 'No protection', 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.', cite: 'CJEU C-311/18 (Schrems II, 2020)',
pulls: [
{ v: '2020', l: 'CJEU invalidates EU-US Privacy Shield' },
{ v: '0', l: 'legally safe paths for cloud data via US providers' },
{ v: 'CLOUD Act', l: 'applies extraterritorially to US parent companies' },
],
},
{ kicker: 'REGULATION TSUNAMI', 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.', cite: 'VDMA Compliance Costs Manufacturing 2024',
pulls: [
{ v: '30 000+', l: 'DE companies directly hit by NIS2' },
{ v: '€15-40k', l: 'per external pentest, once a year' },
{ v: '€10-25k', l: 'per CE software risk assessment' },
],
},
]
export function PrintProblemPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
const de = lang === 'de'
const cards = de ? DE_PROBLEM_CARDS : EN_PROBLEM_CARDS
return (
<Page kicker="03" section={de ? 'DAS PROBLEM' : 'THE PROBLEM'} title={de ? 'Deutsche Unternehmen wollen KI, nicht um den Preis ihrer Datensouveränität.' : 'German companies want AI, not at the cost of their data sovereignty.'} subtitle={de ? 'Drei strukturelle Spannungen blockieren produzierende Unternehmen in der KI-Transformation.' : 'Three structural tensions are blocking manufacturing companies in the AI transformation.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName} footnote={cards.map(c => c.cite).join(' · ')}>
<ThreeCol cols={cards.map((c, i) => (
<div key={i} style={{ borderLeft: `2px solid ${COLORS.red600}`, paddingLeft: '5mm', height: '100%', display: 'flex', flexDirection: 'column' }}>
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.red700, textTransform: 'uppercase', letterSpacing: '0.12em', marginBottom: '3mm' }}>{c.kicker}</div>
<div style={{ fontSize: '20pt', fontWeight: 800, color: COLORS.slate900, lineHeight: 1, letterSpacing: '-0.02em', marginBottom: '4mm' }}>{c.stat}</div>
<div style={{ fontSize: '9pt', color: COLORS.slate700, lineHeight: 1.55, marginBottom: '4mm' }}>{c.desc}</div>
{/* bottom stat block fills empty space and adds visual punch */}
<div style={{ marginTop: 'auto', paddingTop: '4mm', borderTop: `1px solid ${COLORS.slate200}`, display: 'flex', flexDirection: 'column', gap: '3mm' }}>
{c.pulls.map((p, j) => (
<div key={j} style={{ display: 'flex', alignItems: 'baseline', gap: '4mm' }}>
<div style={{ fontSize: '13pt', fontWeight: 800, color: COLORS.red700, fontVariantNumeric: 'tabular-nums', letterSpacing: '-0.02em', minWidth: '22mm' }}>{p.v}</div>
<div style={{ fontSize: '7.5pt', color: COLORS.slate600, lineHeight: 1.4, flex: 1 }}>{p.l}</div>
</div>
))}
</div>
</div>
))} />
<div style={{ marginTop: '6mm', flexShrink: 0 }}>
<Callout tone="caution" label={de ? 'Die Konsequenz' : 'The Consequence'}>
{de
? '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.'
: 'Manufacturing companies need an AI solution that runs in Europe, protects their code and automates compliance, without giving their data to US corporations.'}
</Callout>
</div>
</Page>
)
}
/* ===== SOLUTION ===== */
const DE_PILLARS = [
{ kicker: 'PILLAR 01', t: 'Kontinuierliche Code-Security', d: '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.', stat: { v: '€15k+', l: 'Pentest-Kosten gespart / App / Jahr' },
bullets: [
'SAST + DAST + SBOM bei jedem Push (Semgrep, Gitleaks, Syft, Trivy)',
'KI-Triage filtert False-Positives auf <2%',
'LLM-basierter Auto-Fix in CI/CD direkt im Pull Request',
'Autonomes KI-Pentesting mit Angriffsketten + Exploitability',
'Integration in Jira, GitHub, GitLab, Azure DevOps',
] },
{ kicker: 'PILLAR 02', t: 'Compliance auf Autopilot', d: 'VVT, TOMs, DSFA, Löschfristen und CE-Risikobeurteilung werden automatisch generiert. Nach dem Audit: Abweichungen End-to-End mit Rollen, Stichtagen, Tickets und Eskalation an die GF.', stat: { v: '80%', l: 'Zeitersparnis bei Compliance-Prüfungen' },
bullets: [
'Auto-Generierung VVT (Art. 30 DSGVO) bei jeder Code-Änderung',
'CE-Software-Risikobeurteilung auf Code-Basis (MaschVO 2023)',
'Audit Manager: Tickets → Nachweise → GF-Eskalation bei SLA-Bruch',
'Compliance LLM mit Quellenangabe — auditierbar zitierbar',
'Tender Matching: RFQs in Stunden statt Wochen beantworten',
] },
{ kicker: 'PILLAR 03', t: 'Deutsche Cloud, volle Integration', d: 'BSI-zertifizierte Cloud in Deutschland (SysEleven, IONOS). Live-Support über Jitsi und Matrix. Keine US-SaaS im Source Code. Optional Mac Mini/Studio für absolute Privacy.', stat: { v: '100%', l: 'EU-Hosting, keine US-Anbieter' },
bullets: [
'BSI C5 zertifizierte Cloud-Hoster in DE und FR',
'Self-Hosted Matrix (Chat) + Jitsi (Video) für Support-Calls',
'Lokale LLM-Inferenz (Qwen3, DeepSeek) — air-gap-fähig',
'Optional: Mac Mini/Studio on-premise für Kleinunternehmen',
'Vault, Keycloak, OPA für Secrets, SSO, Policies',
] },
]
const EN_PILLARS = [
{ kicker: 'PILLAR 01', t: 'Continuous Code Security', d: 'SAST, DAST, SBOM and pentesting on every code change, not once a year. Findings land as tickets in your issue tracker, with implementation suggestions.', stat: { v: '€15k+', l: 'pentest costs saved / app / year' },
bullets: [
'SAST + DAST + SBOM on every push (Semgrep, Gitleaks, Syft, Trivy)',
'AI triage lowers false positives to <2%',
'LLM-based auto-fix in CI/CD directly inside the pull request',
'Autonomous AI pentesting with attack chains + exploitability',
'Integration with Jira, GitHub, GitLab, Azure DevOps',
] },
{ kicker: 'PILLAR 02', t: 'Compliance on Autopilot', d: 'RoPA, TOMs, DPIA, retention and CE risk assessment generated automatically. Post-audit: deviations end-to-end with roles, deadlines, tickets and escalation to management.', stat: { v: '80%', l: 'time saved on compliance checks' },
bullets: [
'Auto-generated RoPA (GDPR Art. 30) on every code change',
'CE software risk assessment at code level (Machinery Reg. 2023)',
'Audit Manager: tickets → evidence → mgmt escalation on SLA breach',
'Compliance LLM with citations — audit-citable answers',
'Tender Matching: answer RFQs in hours not weeks',
] },
{ kicker: 'PILLAR 03', t: 'German Cloud, Full Integration', d: 'BSI-certified cloud in Germany (SysEleven, IONOS). Live support via Jitsi and Matrix. No US SaaS in source code. Optional Mac Mini/Studio for absolute privacy.', stat: { v: '100%', l: 'EU hosting, no US providers' },
bullets: [
'BSI C5 certified cloud hosts in DE and FR',
'Self-hosted Matrix (chat) + Jitsi (video) for support calls',
'Local LLM inference (Qwen3, DeepSeek) — air-gap capable',
'Optional: Mac Mini/Studio on-premise for micro businesses',
'Vault, Keycloak, OPA for secrets, SSO, policies',
] },
]
export function PrintSolutionPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
const de = lang === 'de'
const pillars = de ? DE_PILLARS : EN_PILLARS
return (
<Page kicker="04" section={de ? 'DIE LÖSUNG' : 'THE SOLUTION'} title={de ? 'Kontinuierliche Software-Compliance statt jährlicher Stichproben.' : 'Continuous software compliance instead of annual spot checks.'} subtitle={de ? 'Drei Säulen, Code-Security, Compliance-Automatisierung, EU-Souveränität, auf einer integrierten Plattform.' : 'Three pillars, code security, compliance automation, EU sovereignty, on one integrated platform.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<ThreeCol cols={pillars.map((p, i) => (
<div key={i} style={{ borderLeft: `2px solid ${COLORS.indigo600}`, paddingLeft: '5mm', height: '100%', display: 'flex', flexDirection: 'column' }}>
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.indigo600, textTransform: 'uppercase', letterSpacing: '0.12em', marginBottom: '3mm' }}>{p.kicker}</div>
<div style={{ fontSize: '14pt', fontWeight: 700, color: COLORS.slate900, lineHeight: 1.2, letterSpacing: '-0.005em', marginBottom: '4mm' }}>{p.t}</div>
<div style={{ fontSize: '8.5pt', color: COLORS.slate700, lineHeight: 1.5, marginBottom: '4mm' }}>{p.d}</div>
{/* Detail bullets fill the remaining vertical space */}
<div style={{ flex: 1, minHeight: 0 }}>
<Bullets dense items={p.bullets} />
</div>
<div style={{ marginTop: '4mm', paddingTop: '3mm', borderTop: `1px solid ${COLORS.slate200}` }}>
<div style={{ fontSize: '22pt', fontWeight: 800, color: COLORS.emerald700, lineHeight: 1, fontVariantNumeric: 'tabular-nums' }}>{p.stat.v}</div>
<div style={{ fontSize: '7.5pt', color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.08em', marginTop: '1.5mm', fontWeight: 600 }}>{p.stat.l}</div>
</div>
</div>
))} />
<div style={{ marginTop: '6mm', flexShrink: 0 }}>
<Callout tone="accent" label={de ? 'Wie es zusammenkommt' : 'How it comes together'}>
{de
? 'BreakPilot ist die einzige Plattform, die kontinuierliche Code-Security, automatisierte Compliance-Dokumentation und CE-Risikobeurteilung in EU-souveräner Infrastruktur vereint, eine Plattform, ein Audit, eine Rechnung.'
: 'BreakPilot is the only platform that unifies continuous code security, automated compliance documentation and CE risk assessment on EU-sovereign infrastructure, one platform, one audit, one bill.'}
</Callout>
</div>
</Page>
)
}
@@ -1,11 +1,382 @@
import React from 'react'
const INDIGO = '#6366f1'
const INDIGO_LIGHT = '#eef2ff'
const TEXT_DARK = '#1e1b4b'
const TEXT_MED = '#374151'
const TEXT_LIGHT = '#6b7280'
const BORDER = '#e0e7ff'
/* ===== DESIGN TOKENS ===== */
/**
* Adapted from Claude Design tokens (light theme).
* Primary accent = violet (#7c3aed). The names `indigo*` are kept as aliases
* so existing slide files don't need to be touched the *values* now resolve
* to the violet palette. New code can use the explicit `violet*` names.
*/
export const COLORS = {
// Body text — deep purple-tinted instead of pure slate
slate900: '#1a0f34',
slate800: '#2a1f4a',
slate700: 'rgba(26,15,52,.88)',
slate600: 'rgba(26,15,52,.72)',
slate500: 'rgba(26,15,52,.60)',
slate400: 'rgba(26,15,52,.46)',
slate300: 'rgba(26,15,52,.28)',
slate200: 'rgba(26,15,52,.14)',
slate100: 'rgba(26,15,52,.06)',
slate50: 'rgba(26,15,52,.03)',
// Violet palette (new primary accent)
violet900: '#3b0e7a',
violet800: '#5b21b6',
violet700: '#6d28d9',
violet600: '#7c3aed',
violet500: '#8b5cf6',
violet400: '#a78bfa',
violet300: '#c4b5fd',
violet200: '#ddd6fe',
violet100: '#ede9fe',
violet50: '#f5f3ff',
// Legacy `indigo*` aliases — kept so existing slide code compiles unchanged
indigo700: '#6d28d9',
indigo600: '#7c3aed',
indigo500: '#8b5cf6',
indigo50: '#f5f3ff',
// Functional accents
emerald700: '#047857',
emerald600: '#059669',
emerald50: '#ecfdf5',
red700: '#b91c1c',
red600: '#dc2626',
red50: '#fef2f2',
amber700: '#b45309',
amber600: '#d97706',
amber50: '#fffbeb',
// Legacy aliases used by some callers
dark: '#1a0f34',
med: 'rgba(26,15,52,.88)',
light: 'rgba(26,15,52,.60)',
border: 'rgba(26,15,52,.14)',
indigo: '#7c3aed',
indigoLight: '#f5f3ff',
}
const FONT = "'Inter', 'Plus Jakarta Sans', system-ui, -apple-system, sans-serif"
const MONO_FONT = "'JetBrains Mono', ui-monospace, Menlo, Consolas, monospace"
/* ===== PAGE WRAPPER ===== */
interface PageProps {
kicker: string // "03"
section: string // "DAS PROBLEM"
title: string // "Deutsche Unternehmen wollen KI ..."
subtitle?: string
pageNum: number
totalPages: number
versionName: string
children: React.ReactNode
footnote?: React.ReactNode // optional footnote line above footer
}
export function Page({ kicker, section, title, subtitle, pageNum, totalPages, versionName, children, footnote }: PageProps) {
return (
<div className="print-page-break">
<div className="print-page print-page-bg" style={{
width: '297mm',
height: '210mm',
color: COLORS.slate900,
fontFamily: FONT,
display: 'flex',
flexDirection: 'column',
boxSizing: 'border-box',
padding: '11mm 14mm 8mm 14mm',
// screen-only decoration
margin: '0 auto 24px',
boxShadow: '0 4px 24px rgba(59,26,122,0.10)',
WebkitPrintColorAdjust: 'exact',
printColorAdjust: 'exact',
}}>
{/* TITLE BLOCK — left-rule preserved */}
<div style={{ display: 'flex', alignItems: 'stretch', gap: '7mm', marginBottom: '5mm', flexShrink: 0 }}>
<div style={{ width: '3px', background: COLORS.violet600, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact', alignSelf: 'stretch', minHeight: '14mm' }} />
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'baseline', gap: '4mm', marginBottom: '2mm' }}>
<span className="print-mono" style={{ fontFamily: MONO_FONT, fontSize: '7.5pt', fontWeight: 700, letterSpacing: '0.18em', color: COLORS.violet600, textTransform: 'uppercase' }}>
{kicker} &middot; {section}
</span>
<span style={{ flex: 1, height: '1px', background: `linear-gradient(90deg, ${COLORS.violet400}, transparent 80%)`, alignSelf: 'center', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
<span className="print-mono" style={{ fontFamily: MONO_FONT, fontSize: '7pt', color: COLORS.slate500, fontWeight: 500, letterSpacing: '0.06em' }}>BreakPilot &middot; ComplAI</span>
</div>
<h1 style={{ fontSize: '22pt', fontWeight: 800, color: COLORS.slate900, margin: 0, lineHeight: 1.1, letterSpacing: '-0.015em' }}>
{title}
</h1>
{subtitle && (
<p style={{ fontSize: '10pt', color: COLORS.slate600, margin: '2mm 0 0', fontWeight: 400, lineHeight: 1.45, maxWidth: '210mm' }}>
{subtitle}
</p>
)}
{/* Subtle violet accent gradient bar — echo of the Claude Design feel */}
<div style={{ marginTop: '3mm', height: '2px', width: '100%', background: `linear-gradient(90deg, ${COLORS.violet700} 0%, ${COLORS.violet400} 50%, ${COLORS.violet700} 100%)`, opacity: 0.85, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
</div>
</div>
{/* CONTENT */}
<div style={{ flex: 1, minHeight: 0, overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
{children}
</div>
{/* FOOTNOTE (optional) */}
{footnote && (
<div style={{ flexShrink: 0, marginTop: '3mm', paddingTop: '2mm', borderTop: `1px solid ${COLORS.slate200}`, fontSize: '7pt', color: COLORS.slate500, lineHeight: 1.4 }}>
{footnote}
</div>
)}
{/* FOOTER — JetBrains Mono caps to match Claude Design */}
<div className="print-mono" style={{ fontFamily: MONO_FONT, flexShrink: 0, marginTop: '3mm', paddingTop: '2mm', borderTop: `1px solid ${COLORS.slate200}`, display: 'flex', alignItems: 'center', justifyContent: 'space-between', fontSize: '7pt', color: COLORS.slate500, letterSpacing: '0.16em', textTransform: 'uppercase', fontWeight: 700 }}>
<span>BreakPilot &middot; ComplAI</span>
<span style={{ color: COLORS.violet600 }}>{versionName}</span>
<span style={{ fontVariantNumeric: 'tabular-nums' }}>{String(pageNum).padStart(2, '0')} / {String(totalPages).padStart(2, '0')}</span>
</div>
</div>
</div>
)
}
/* ===== KPI ROW ===== */
interface KpiItem { n: string; label: string; tone?: 'default' | 'positive' | 'negative' | 'accent' }
export function KpiRow({ items, align = 'left' }: { items: KpiItem[]; align?: 'left' | 'center' }) {
return (
<div style={{ display: 'grid', gridTemplateColumns: `repeat(${items.length}, 1fr)`, gap: '4mm', borderTop: `1px solid ${COLORS.slate200}`, borderBottom: `1px solid ${COLORS.slate200}`, padding: '4mm 0', margin: '2mm 0' }}>
{items.map((it, i) => {
const color = it.tone === 'positive' ? COLORS.emerald700
: it.tone === 'negative' ? COLORS.red700
: it.tone === 'accent' ? COLORS.indigo600
: COLORS.slate900
return (
<div key={i} style={{ textAlign: align }}>
<div style={{ fontSize: '24pt', fontWeight: 800, color, lineHeight: 1, letterSpacing: '-0.02em', fontVariantNumeric: 'tabular-nums' }}>
{it.n}
</div>
<div style={{ marginTop: '2mm', fontSize: '7.5pt', fontWeight: 600, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.08em', lineHeight: 1.3 }}>
{it.label}
</div>
</div>
)
})}
</div>
)
}
/* ===== COLUMNS ===== */
export function TwoCol({ left, right, ratio = '1:1', gap = '8mm' }: { left: React.ReactNode; right: React.ReactNode; ratio?: '1:1' | '1:1.5' | '1.5:1' | '1:2' | '2:1'; gap?: string }) {
const [l, r] = ratio.split(':').map(Number)
return (
<div style={{ display: 'grid', gridTemplateColumns: `${l}fr ${r}fr`, gap, flex: 1, minHeight: 0 }}>
<div style={{ minWidth: 0, display: 'flex', flexDirection: 'column' }}>{left}</div>
<div style={{ minWidth: 0, display: 'flex', flexDirection: 'column' }}>{right}</div>
</div>
)
}
export function ThreeCol({ cols, gap = '6mm', equalHeight = true }: { cols: React.ReactNode[]; gap?: string; equalHeight?: boolean }) {
return (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap, ...(equalHeight ? { flex: 1, minHeight: 0 } : {}) }}>
{cols.map((c, i) => <div key={i} style={{ minWidth: 0, display: 'flex', flexDirection: 'column' }}>{c}</div>)}
</div>
)
}
export function FourCol({ cols, gap = '4mm' }: { cols: React.ReactNode[]; gap?: string }) {
return (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap }}>
{cols.map((c, i) => <div key={i} style={{ minWidth: 0 }}>{c}</div>)}
</div>
)
}
/* ===== PANEL (left-rule narrative card) ===== */
interface PanelProps {
label?: string
title?: string
tone?: 'neutral' | 'positive' | 'negative' | 'caution' | 'accent'
children: React.ReactNode
dense?: boolean
}
export function Panel({ label, title, tone = 'neutral', children, dense }: PanelProps) {
const color = tone === 'positive' ? COLORS.emerald700
: tone === 'negative' ? COLORS.red700
: tone === 'caution' ? COLORS.amber700
: tone === 'accent' ? COLORS.indigo600
: COLORS.slate600
return (
<div style={{ borderLeft: `2px solid ${color}`, paddingLeft: dense ? '4mm' : '5mm', paddingRight: '2mm', flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }}>
{label && (
<div style={{ fontSize: '7.5pt', fontWeight: 700, color, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '2mm' }}>
{label}
</div>
)}
{title && (
<div style={{ fontSize: '12pt', fontWeight: 700, color: COLORS.slate900, marginBottom: '3mm', lineHeight: 1.25, letterSpacing: '-0.005em' }}>
{title}
</div>
)}
<div style={{ flex: 1, minHeight: 0, fontSize: '9pt', color: COLORS.slate700, lineHeight: 1.55 }}>
{children}
</div>
</div>
)
}
/* ===== BULLETS ===== */
interface BulletsProps { items: (string | React.ReactNode)[]; dense?: boolean; tone?: 'neutral' | 'positive' | 'negative' | 'accent' }
export function Bullets({ items, dense, tone = 'neutral' }: BulletsProps) {
const dotColor = tone === 'positive' ? COLORS.emerald600
: tone === 'negative' ? COLORS.red600
: tone === 'accent' ? COLORS.indigo600
: COLORS.slate500
return (
<ul style={{ listStyle: 'none', margin: 0, padding: 0 }}>
{items.map((item, i) => (
<li key={i} style={{ position: 'relative', paddingLeft: '5mm', marginBottom: dense ? '1.5mm' : '2.5mm', fontSize: dense ? '8.5pt' : '9pt', color: COLORS.slate700, lineHeight: 1.5 }}>
<span style={{ position: 'absolute', left: 0, top: dense ? '4pt' : '4.5pt', width: '3mm', height: '0.5pt', background: dotColor }} />
{item}
</li>
))}
</ul>
)
}
/* ===== DATA TABLE ===== */
interface TableCol {
header: string
width?: string
align?: 'left' | 'right' | 'center'
numeric?: boolean
}
interface DataTableProps {
cols: TableCol[]
rows: (string | number | React.ReactNode)[][]
dense?: boolean
zebra?: boolean
highlightFirstCol?: boolean
}
export function DataTable({ cols, rows, dense, zebra = true, highlightFirstCol }: DataTableProps) {
const fontSize = dense ? '7.5pt' : '8.5pt'
const padY = dense ? '1.5mm' : '2mm'
return (
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize, color: COLORS.slate800, fontVariantNumeric: 'tabular-nums' }}>
<thead>
<tr>
{cols.map((c, i) => (
<th key={i} style={{
background: COLORS.indigo50,
color: COLORS.indigo700,
fontWeight: 700,
textTransform: 'uppercase',
letterSpacing: '0.08em',
fontSize: dense ? '6.5pt' : '7pt',
padding: `${padY} 2.5mm`,
textAlign: c.align || (c.numeric ? 'right' : 'left'),
width: c.width,
borderBottom: `1px solid ${COLORS.slate200}`,
WebkitPrintColorAdjust: 'exact',
printColorAdjust: 'exact',
}}>
{c.header}
</th>
))}
</tr>
</thead>
<tbody>
{rows.map((row, ri) => (
<tr key={ri} style={{ background: zebra && ri % 2 === 1 ? COLORS.slate50 : 'transparent', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
{row.map((cell, ci) => (
<td key={ci} style={{
padding: `${padY} 2.5mm`,
textAlign: cols[ci]?.align || (cols[ci]?.numeric ? 'right' : 'left'),
borderBottom: `1px solid ${COLORS.slate100}`,
verticalAlign: 'top',
lineHeight: 1.4,
fontWeight: highlightFirstCol && ci === 0 ? 600 : 400,
color: highlightFirstCol && ci === 0 ? COLORS.slate900 : COLORS.slate700,
whiteSpace: cols[ci]?.numeric ? 'nowrap' : 'normal',
}}>
{cell}
</td>
))}
</tr>
))}
</tbody>
</table>
)
}
/* ===== FEATURE MATRIX (●/○/—) ===== */
export type Glyph = true | false | 'partial'
export function MatrixGlyph({ v, isUSP }: { v: Glyph; isUSP?: boolean }) {
if (v === true) return <span style={{ color: isUSP ? COLORS.indigo600 : COLORS.slate800, fontWeight: 700 }}>&#9679;</span>
if (v === 'partial') return <span style={{ color: COLORS.amber700 }}>&#9681;</span>
return <span style={{ color: COLORS.slate300 }}>&mdash;</span>
}
/* ===== CALLOUT ===== */
interface CalloutProps {
tone?: 'neutral' | 'positive' | 'negative' | 'caution' | 'accent'
label?: string
children: React.ReactNode
}
export function Callout({ tone = 'neutral', label, children }: CalloutProps) {
const color = tone === 'positive' ? COLORS.emerald700
: tone === 'negative' ? COLORS.red700
: tone === 'caution' ? COLORS.amber700
: tone === 'accent' ? COLORS.indigo600
: COLORS.slate600
const bg = tone === 'positive' ? COLORS.emerald50
: tone === 'negative' ? COLORS.red50
: tone === 'caution' ? COLORS.amber50
: tone === 'accent' ? COLORS.indigo50
: COLORS.slate50
return (
<div style={{ borderLeft: `3px solid ${color}`, background: bg, padding: '3mm 4mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
{label && (
<div style={{ fontSize: '7.5pt', fontWeight: 700, color, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '1.5mm' }}>{label}</div>
)}
<div style={{ fontSize: '9pt', color: COLORS.slate800, lineHeight: 1.5 }}>{children}</div>
</div>
)
}
/* ===== DIVIDER ===== */
export function Divider({ space = '4mm' }: { space?: string }) {
return <div style={{ height: '1px', background: COLORS.slate200, margin: `${space} 0` }} />
}
/* ===== STAT INLINE (label: value) ===== */
export function StatLine({ label, value, tone = 'neutral' }: { label: string; value: string; tone?: 'neutral' | 'positive' | 'negative' | 'accent' }) {
const color = tone === 'positive' ? COLORS.emerald700
: tone === 'negative' ? COLORS.red700
: tone === 'accent' ? COLORS.indigo600
: COLORS.slate900
return (
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', padding: '1.5mm 0', borderBottom: `1px solid ${COLORS.slate100}`, fontSize: '8.5pt' }}>
<span style={{ color: COLORS.slate600 }}>{label}</span>
<span style={{ fontWeight: 700, color, fontVariantNumeric: 'tabular-nums' }}>{value}</span>
</div>
)
}
/* ===== LEGACY EXPORTS (preserved for any callers not yet migrated) ===== */
interface PrintPageProps {
title: string
@@ -16,83 +387,26 @@ interface PrintPageProps {
}
export function PrintPage({ title, pageNum, totalPages, versionName, children }: PrintPageProps) {
// Legacy wrapper: maps to the new Page primitive without title/subtitle structure
return (
<div className="print-page-break">
<div className="print-page" style={{
width: '297mm',
minHeight: '210mm',
height: '210mm',
backgroundColor: '#ffffff',
color: TEXT_DARK,
overflow: 'hidden',
display: 'flex',
flexDirection: 'column',
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif',
boxSizing: 'border-box',
// screen-only decoration
margin: '0 auto 32px',
boxShadow: '0 4px 24px rgba(0,0,0,0.12)',
}}>
{/* Header bar */}
<div style={{
height: '32px',
backgroundColor: INDIGO,
WebkitPrintColorAdjust: 'exact',
printColorAdjust: 'exact',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '0 18px',
flexShrink: 0,
}}>
<span style={{ color: '#fff', fontWeight: 700, fontSize: '12px', letterSpacing: '0.02em' }}>BreakPilot</span>
<span style={{ color: 'rgba(255,255,255,0.85)', fontSize: '11px' }}>{title}</span>
</div>
{/* Content area — must stretch to fill all remaining height */}
<div style={{
flex: 1,
overflow: 'hidden',
padding: '16px 22px',
display: 'flex',
flexDirection: 'column',
minHeight: 0,
}}>
{children}
</div>
{/* Footer bar */}
<div style={{
height: '24px',
borderTop: `1px solid ${BORDER}`,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '0 18px',
flexShrink: 0,
}}>
<span style={{ fontSize: '9px', color: TEXT_LIGHT }}>{versionName}</span>
<span style={{ fontSize: '9px', color: INDIGO, fontWeight: 600, letterSpacing: '0.08em', textTransform: 'uppercase' }}>CONFIDENTIAL</span>
<span style={{ fontSize: '9px', color: TEXT_LIGHT }}>{pageNum} / {totalPages}</span>
</div>
</div>
</div>
<Page
kicker=""
section={title.toUpperCase()}
title={title}
pageNum={pageNum}
totalPages={totalPages}
versionName={versionName}
>
{children}
</Page>
)
}
interface SectionTitleProps { children: React.ReactNode; subtitle?: string }
export function SectionTitle({ children, subtitle }: SectionTitleProps) {
export function SectionTitle({ children, subtitle }: { children: React.ReactNode; subtitle?: string }) {
return (
<div style={{ marginBottom: '12px', flexShrink: 0 }}>
<h2 style={{ fontSize: '19px', fontWeight: 700, color: TEXT_DARK, borderLeft: `3px solid ${INDIGO}`, paddingLeft: '10px', margin: 0, lineHeight: 1.3 }}>
{children}
</h2>
{subtitle && (
<p style={{ fontSize: '11px', color: TEXT_LIGHT, marginTop: '4px', marginLeft: '13px', marginBottom: 0 }}>
{subtitle}
</p>
)}
<div style={{ marginBottom: '4mm' }}>
<h2 style={{ fontSize: '14pt', fontWeight: 700, color: COLORS.slate900, margin: 0, lineHeight: 1.2 }}>{children}</h2>
{subtitle && <p style={{ fontSize: '9pt', color: COLORS.slate600, marginTop: '1.5mm', marginBottom: 0 }}>{subtitle}</p>}
</div>
)
}
@@ -104,50 +418,14 @@ interface TableProps {
}
export function PrintTable({ headers, rows, colWidths }: TableProps) {
return (
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '10px' }}>
<thead>
<tr>
{headers.map((h, i) => (
<th key={i} style={{
backgroundColor: INDIGO_LIGHT,
color: TEXT_DARK,
fontWeight: 700,
textTransform: 'uppercase',
letterSpacing: '0.04em',
fontSize: '9px',
padding: '6px 9px',
textAlign: 'left',
width: colWidths?.[i],
WebkitPrintColorAdjust: 'exact',
printColorAdjust: 'exact',
}}>
{h}
</th>
))}
</tr>
</thead>
<tbody>
{rows.map((row, ri) => (
<tr key={ri} style={{ backgroundColor: ri % 2 === 0 ? '#ffffff' : '#fafafa' }}>
{row.map((cell, ci) => (
<td key={ci} style={{ padding: '6px 9px', color: TEXT_MED, borderBottom: `1px solid ${BORDER}`, verticalAlign: 'top', lineHeight: 1.5 }}>
{cell}
</td>
))}
</tr>
))}
</tbody>
</table>
)
const cols = headers.map((h, i) => ({ header: h, width: colWidths?.[i] }))
return <DataTable cols={cols} rows={rows} />
}
export function Badge({ children, color = INDIGO }: { children: React.ReactNode; color?: string }) {
export function Badge({ children, color = COLORS.indigo600 }: { children: React.ReactNode; color?: string }) {
return (
<span style={{ display: 'inline-block', padding: '2px 8px', borderRadius: '99px', backgroundColor: `${color}22`, color, fontSize: '9px', fontWeight: 600 }}>
<span style={{ display: 'inline-block', padding: '0.5mm 2mm', borderRadius: '2pt', background: `${color}1f`, color, fontSize: '7.5pt', fontWeight: 600, letterSpacing: '0.02em', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
{children}
</span>
)
}
export const COLORS = { indigo: INDIGO, indigoLight: INDIGO_LIGHT, dark: TEXT_DARK, med: TEXT_MED, light: TEXT_LIGHT, border: BORDER }
@@ -0,0 +1,414 @@
import { Language, PitchMarket, PitchTeamMember, PitchMilestone, PitchFunding } from '@/lib/types'
import { Page, Callout, COLORS, DataTable, StatLine } from './PrintLayout'
import { MarketFunnel, ComparisonBars, DonutChart } from './PrintCharts'
interface SlideBase { lang: Language; pageNum: number; totalPages: number; versionName: string }
function fmtEur(v: number, de: boolean) {
if (v >= 1e9) return de ? `${(v / 1e9).toFixed(1).replace('.', ',')} Mrd. €` : `${(v / 1e9).toFixed(1)}B`
if (v >= 1e6) return de ? `${(v / 1e6).toFixed(0)} Mio. €` : `${(v / 1e6).toFixed(0)}M`
if (v >= 1e3) return de ? `${(v / 1e3).toFixed(0)}k €` : `${(v / 1e3).toFixed(0)}k`
return de ? `${v}` : `${v}`
}
/* ===== MARKET ===== */
export function PrintMarketPage({ market, lang, pageNum, totalPages, versionName }: SlideBase & { market: PitchMarket[] }) {
const de = lang === 'de'
const tam = market.find(m => m.market_segment === 'TAM')
const sam = market.find(m => m.market_segment === 'SAM')
const som = market.find(m => m.market_segment === 'SOM')
return (
<Page kicker="09" section={de ? 'MARKT' : 'MARKET'} title={de ? 'Compliance & Code-Security für produzierende Unternehmen.' : 'Compliance & code security for manufacturing companies.'} subtitle={de ? 'Validierter Markt: Top-10 Compliance-Anbieter erwirtschaften >$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'}>
<div style={{ display: 'grid', gridTemplateColumns: '1.3fr 1fr', gap: '8mm', flex: 1, minHeight: 0 }}>
<div>
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '3mm' }}>{de ? 'Marktdimensionierung' : 'Market sizing'}</div>
{tam && sam && som && (
<MarketFunnel
tam={{ value: tam.value_eur, label: de ? 'Total Addressable' : 'Total Addressable', growth: tam.growth_rate_pct ?? 14, note: de ? 'Globaler Compliance- und GRC-Markt (alle Branchen, alle Größen).' : 'Global compliance and GRC market (all industries, all sizes).' }}
sam={{ value: sam.value_eur, label: de ? 'Serviceable Addressable' : 'Serviceable Addressable', growth: sam.growth_rate_pct ?? 18, note: de ? 'DACH + EU: regulierte Branchen, KMU und Enterprise.' : 'DACH + EU: regulated industries, SMB and enterprise.' }}
som={{ value: som.value_eur, label: de ? 'Kernmarkt 5 Jahre' : 'Core market 5 yrs', growth: som.growth_rate_pct ?? 25, note: de ? 'Anlagen- und Maschinenbau DACH, unser Kernsegment.' : 'Machine and plant manufacturing DACH, our core segment.' }}
fmt={(v) => fmtEur(v, de)}
/>
)}
</div>
<div>
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '3mm' }}>{de ? 'Kernsegment: Maschinen- und Anlagenbau DACH' : 'Core segment: Machine & plant manufacturing DACH'}</div>
<DataTable
cols={[
{ header: de ? 'Kennzahl' : 'Metric', width: '50%' },
{ header: de ? 'Wert' : 'Value', numeric: true },
]}
rows={[
[de ? 'Unternehmen DACH' : 'Companies DACH', '~6.500'],
[de ? 'Davon 10500 MA (Zielgröße)' : 'Of which 10500 emp. (target)', '~4.200'],
[de ? 'Beschäftigte gesamt' : 'Total employees', '~1,3 Mio.'],
[de ? 'Umsatz Branche p.a.' : 'Industry revenue p.a.', de ? '~€280 Mrd.' : '~€280B'],
[de ? 'Compliance-Budget Ø' : 'Avg. compliance budget', de ? '€50150k / Jahr' : '€50150k / yr'],
[de ? 'Validierte ARR Top-10' : 'Validated ARR top-10', '>$1,1 Mrd.'],
]}
dense
highlightFirstCol
/>
<div style={{ marginTop: '5mm' }}>
<Callout tone="accent" label={de ? 'Warum Maschinenbau zuerst' : 'Why manufacturing first'}>
{de
? 'Höchste Regulierungsdichte (DSGVO + AI Act + CRA + Maschinen-VO + ProdSG + LkSG) bei gleichzeitig kleinem Compliance-Team. Klare Schmerzpunkte. Bekannte Vertriebskanäle (VDMA, IHK, Messen).'
: 'Highest regulation density (GDPR + AI Act + CRA + Machinery Reg. + ProdSG + LkSG) with simultaneously small compliance teams. Clear pain points. Known sales channels (VDMA, Chamber of Commerce, fairs).'}
</Callout>
</div>
</div>
</div>
</Page>
)
}
/* ===== MILESTONES / TRACTION ===== */
const MS_DE = [
{ d: 'Okt 2025', t: 'Gründerzuschuss & IHK Konstanz', s: 'done' },
{ d: '11 Nov 2025', t: 'DPMA-Markenanmeldung BreakPilot', s: 'done' },
{ d: '21 Nov 2025', t: 'Domain-Portfolio (.com, .de, .ai + Typo)', s: 'done' },
{ d: 'Jan 2026', t: 'Plattform-Entwicklung gestartet (500K+ LoC)', s: 'done' },
{ d: '27 Mär 2026', t: 'DPMA-Markeneintragung BreakPilot', s: 'done' },
{ d: 'Apr 2026', t: 'RAG mit 375+ Dokumenten · 25k+ Controls', s: 'done' },
{ d: '01 Mai 2026', t: 'EUIPO-Markenanmeldung (EU-weit)', s: 'next' },
{ d: 'Aug 2026', t: 'GmbH-Gründung Breakpilot COMPLAI', s: 'planned' },
{ d: 'Aug 2026', t: '2 zahlende Pilotkunden, erste Umsätze', s: 'planned' },
{ d: 'Q3 2026', t: 'Public Beta-Launch', s: 'planned' },
]
const MS_EN = [
{ d: 'Oct 2025', t: 'Founder grant & IHK Konstanz', s: 'done' },
{ d: '11 Nov 2025', t: 'DPMA trademark filing BreakPilot', s: 'done' },
{ d: '21 Nov 2025', t: 'Domain portfolio (.com, .de, .ai + typos)', s: 'done' },
{ d: 'Jan 2026', t: 'Platform development started (500K+ LoC)', s: 'done' },
{ d: '27 Mar 2026', t: 'DPMA trademark registration', s: 'done' },
{ d: 'Apr 2026', t: 'RAG with 375+ documents · 25k+ controls', s: 'done' },
{ d: '01 May 2026', t: 'EUIPO trademark filing (EU-wide)', s: 'next' },
{ d: 'Aug 2026', t: 'GmbH incorporation Breakpilot COMPLAI', s: 'planned' },
{ d: 'Aug 2026', t: '2 paying pilot customers, first revenue', s: 'planned' },
{ d: 'Q3 2026', t: 'Public beta launch', s: 'planned' },
]
export function PrintMilestonesPage({ milestones, lang, pageNum, totalPages, versionName }: SlideBase & { milestones: PitchMilestone[] }) {
void milestones // we use the curated list above
const de = lang === 'de'
const items = de ? MS_DE : MS_EN
const dotFor = (s: string) => s === 'done' ? COLORS.emerald600 : s === 'next' ? COLORS.amber600 : COLORS.slate400
return (
<Page kicker="11" section={de ? 'TRACTION & MEILENSTEINE' : 'TRACTION & MILESTONES'} title={de ? 'Was wir bereits erreicht haben, und was als Nächstes kommt.' : 'What we have achieved, and what comes next.'} subtitle={de ? 'Wir gehen mit Substanz: bereits gebaut, nicht nur geplant. 500.000+ Zeilen Code, 25.000+ Controls, Markenschutz gesichert.' : 'We arrive with substance: built, not just planned. 500,000+ lines of code, 25,000+ controls, trademark secured.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
{/* Top KPI strip */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: '4mm', padding: '4mm 0', borderTop: `1px solid ${COLORS.slate200}`, borderBottom: `1px solid ${COLORS.slate200}` }}>
{[
{ n: '500K+', l: de ? 'Lines of Code' : 'Lines of code' },
{ n: '25.000+', l: de ? 'Atomare Controls' : 'Atomic controls' },
{ n: '385', l: de ? 'Gesetze im RAG' : 'Laws in RAG' },
{ n: '12', l: de ? 'Compliance-Module' : 'Compliance modules' },
{ n: '2', l: de ? 'Pilotkunden (Aug 26)' : 'Pilot customers (Aug 26)' },
].map((k, i) => (
<div key={i}>
<div style={{ fontSize: '20pt', fontWeight: 800, color: COLORS.indigo600, lineHeight: 1, fontVariantNumeric: 'tabular-nums' }}>{k.n}</div>
<div style={{ fontSize: '7pt', color: COLORS.slate500, marginTop: '1.5mm', textTransform: 'uppercase', letterSpacing: '0.08em', fontWeight: 600 }}>{k.l}</div>
</div>
))}
</div>
<div style={{ marginTop: '5mm', flex: 1, minHeight: 0, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6mm' }}>
<div>
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '2mm' }}>{de ? 'Erreicht (Okt 2025 Apr 2026)' : 'Achieved (Oct 2025 Apr 2026)'}</div>
{items.filter(i => i.s === 'done').map((m, i) => (
<div key={i} style={{ display: 'flex', alignItems: 'flex-start', gap: '4mm', padding: '2mm 0', borderBottom: `1px solid ${COLORS.slate100}` }}>
<div style={{ flexShrink: 0, marginTop: '2pt', width: '6pt', height: '6pt', borderRadius: '50%', background: dotFor(m.s), WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: '7.5pt', color: COLORS.slate500, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.08em' }}>{m.d}</div>
<div style={{ fontSize: '9pt', color: COLORS.slate900, fontWeight: 600, lineHeight: 1.4, marginTop: '0.5mm' }}>{m.t}</div>
</div>
</div>
))}
</div>
<div>
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '2mm' }}>{de ? 'Nächste 4 Monate' : 'Next 4 months'}</div>
{items.filter(i => i.s !== 'done').map((m, i) => (
<div key={i} style={{ display: 'flex', alignItems: 'flex-start', gap: '4mm', padding: '2mm 0', borderBottom: `1px solid ${COLORS.slate100}` }}>
<div style={{ flexShrink: 0, marginTop: '2pt', width: '6pt', height: '6pt', borderRadius: '50%', background: dotFor(m.s), border: m.s === 'next' ? 'none' : `1px solid ${COLORS.slate400}`, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: '7.5pt', color: COLORS.slate500, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.08em' }}>{m.d} &middot; {m.s === 'next' ? (de ? 'Nächster Schritt' : 'Next step') : (de ? 'Geplant' : 'Planned')}</div>
<div style={{ fontSize: '9pt', color: COLORS.slate900, fontWeight: 600, lineHeight: 1.4, marginTop: '0.5mm' }}>{m.t}</div>
</div>
</div>
))}
</div>
</div>
</Page>
)
}
/* ===== TEAM ===== */
export function PrintTeamPage({ team, lang, pageNum, totalPages, versionName }: SlideBase & { team: PitchTeamMember[] }) {
const de = lang === 'de'
const members = team && team.length ? team : [
{ id: 1, name: 'Benjamin Bönisch', role_de: 'CEO & Co-Founder', role_en: 'CEO & Co-Founder', bio_de: 'Mehrfacher Gründer mit Fokus auf B2B-SaaS und Vertrieb. Ehemals Geschäftsführer und Vertriebsleiter. Tiefe Verankerung im Maschinenbau-Netzwerk DACH.', bio_en: 'Serial founder focused on B2B SaaS and sales. Former CEO and VP Sales. Deep network in DACH manufacturing.', equity_pct: 37.3, expertise: ['B2B Sales', 'Go-to-Market', 'Manufacturing', 'Operations'], linkedin_url: '', photo_url: '' },
{ id: 2, name: 'Sharang Parnerkar', role_de: 'CTO & Co-Founder', role_en: 'CTO & Co-Founder', bio_de: 'Ex-Anthropic, Ex-Google. Distributed systems, KI-Infrastruktur, RAG-Pipelines. Open-Source-Contributor. Hat die gesamte Plattform-Architektur entworfen und 500K+ LoC implementiert.', bio_en: 'Ex-Anthropic, ex-Google. Distributed systems, AI infrastructure, RAG pipelines. Open-source contributor. Designed the entire platform architecture and implemented 500K+ LoC.', equity_pct: 37.3, expertise: ['AI Infrastructure', 'Distributed Systems', 'RAG', 'Go/Python/TypeScript'], linkedin_url: '', photo_url: '' },
]
return (
<Page kicker="13" section={de ? 'TEAM' : 'TEAM'} title={de ? 'Gründer mit Domain-Expertise.' : 'Founders with domain expertise.'} subtitle={de ? 'Komplementäres Gründerduo: Vertrieb + Engineering. Beide haben bereits skaliert. Beide kennen den Markt.' : 'Complementary founding duo: sales + engineering. Both have scaled. Both know the market.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8mm', flex: 1, minHeight: 0 }}>
{[0, 1].map(idx => {
const m = members[idx]
if (!m) return null
return (
<div key={idx} style={{ border: `1px solid ${COLORS.slate200}`, borderTop: `3px solid ${COLORS.indigo600}`, padding: '5mm', display: 'flex', flexDirection: 'column', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<div style={{ display: 'flex', gap: '5mm', alignItems: 'flex-start', marginBottom: '4mm' }}>
{/* Photo */}
<div style={{ width: '32mm', height: '32mm', flexShrink: 0, border: `1px solid ${COLORS.slate200}`, background: COLORS.slate100, overflow: 'hidden', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
{m.photo_url ? (
/* eslint-disable-next-line @next/next/no-img-element */
<img src={m.photo_url} alt={m.name} style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
) : (
<div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '24pt', fontWeight: 800, color: COLORS.slate400, letterSpacing: '-0.02em' }}>
{m.name?.split(' ').map(s => s[0]).slice(0, 2).join('') || '?'}
</div>
)}
</div>
{/* Header text */}
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: '2mm' }}>
<span style={{ fontSize: '7pt', fontWeight: 700, color: COLORS.indigo600, textTransform: 'uppercase', letterSpacing: '0.14em' }}>CO-FOUNDER &middot; {String(idx + 1).padStart(2, '0')} / 02</span>
<span style={{ fontSize: '7pt', color: COLORS.slate500, fontWeight: 700, fontVariantNumeric: 'tabular-nums' }}>{(m.equity_pct ?? 37.3).toLocaleString('de-DE')}% Equity</span>
</div>
<div style={{ fontSize: '18pt', fontWeight: 800, color: COLORS.slate900, lineHeight: 1.05, marginBottom: '1mm', letterSpacing: '-0.015em' }}>{m.name}</div>
<div style={{ fontSize: '10pt', fontWeight: 600, color: COLORS.indigo600 }}>{de ? m.role_de : m.role_en}</div>
</div>
</div>
<div style={{ fontSize: '9pt', color: COLORS.slate700, lineHeight: 1.55, flex: 1 }}>{de ? m.bio_de : m.bio_en}</div>
<div style={{ marginTop: '4mm', paddingTop: '3mm', borderTop: `1px solid ${COLORS.slate200}` }}>
<div style={{ fontSize: '7.5pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '2mm' }}>{de ? 'Expertise' : 'Expertise'}</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '1.5mm' }}>
{(m.expertise || []).map((e, i) => (
<span key={i} style={{ fontSize: '8pt', padding: '0.5mm 2mm', background: COLORS.indigo50, color: COLORS.indigo700, fontWeight: 600, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>{e}</span>
))}
</div>
</div>
</div>
)
})}
</div>
<div style={{ marginTop: '5mm', flexShrink: 0 }}>
<Callout tone="accent" label={de ? 'Equity-Struktur' : 'Equity structure'}>
{de
? 'Gründer 74,6% · Pre-Seed-Investor 20% · ESOP-Pool 5,4%. Beide Gründer mit Vesting (4 Jahre, 1 Jahr Cliff). Keine Side-Projekte, keine externen Verpflichtungen.'
: 'Founders 74.6% · Pre-Seed Investor 20% · ESOP pool 5.4%. Both founders with vesting (4 years, 1 year cliff). No side projects, no external commitments.'}
</Callout>
</div>
</Page>
)
}
/* ===== THE ASK ===== */
function formatTargetDate(raw: string | undefined, de: boolean): string {
if (!raw) return de ? 'Q3 2026' : 'Q3 2026'
// Accept ISO timestamps, ISO dates, or already-formatted strings.
const d = new Date(raw)
if (isNaN(d.getTime())) return raw
const months = de
? ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez']
: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
return `${months[d.getUTCMonth()]} ${d.getUTCFullYear()}`
}
function formatFunding(amount: number): string {
if (amount >= 1_000_000) {
const m = amount / 1_000_000
return '€' + (m % 1 === 0 ? m.toFixed(0) : m.toFixed(1)) + 'M'
}
return '€' + Math.round(amount / 1_000) + 'k'
}
export function PrintTheAskPage({ funding, lang, pageNum, totalPages, versionName }: SlideBase & { funding: PitchFunding }) {
const de = lang === 'de'
const amount = funding?.amount_eur || 1_000_000
const instrument = funding?.instrument || (de ? 'Wandeldarlehen' : 'Convertible Loan')
const isConvertible = (instrument || '').toLowerCase().includes('wandeldarlehen') ||
(instrument || '').toLowerCase().includes('convertible') ||
(instrument || '').toLowerCase().includes('safe')
// For equity rounds we display Pre/Post/Investor-Share computed from a 20%
// assumed investor share. For convertibles those fields don't apply — show
// typical convertible terms instead (Discount, Maturity, Interest).
const equityShare = 0.20
const postMoney = amount / equityShare
const preMoney = postMoney - amount
const fmtBig = (n: number) => n >= 1_000_000
? '€' + (n / 1_000_000).toFixed(n % 1_000_000 === 0 ? 0 : 1) + 'M'
: '€' + Math.round(n / 1_000) + 'k'
const termTiles: [string, string][] = isConvertible
? [
[de ? 'Funding' : 'Funding', formatFunding(amount)],
[de ? 'Discount' : 'Discount', '20%'],
[de ? 'Laufzeit' : 'Maturity', de ? '24 Monate' : '24 months'],
[de ? 'INVEST-Zuschuss' : 'INVEST grant', '20%'],
]
: [
[de ? 'Funding' : 'Funding', formatFunding(amount)],
[de ? 'Pre-Money' : 'Pre-money', fmtBig(preMoney)],
[de ? 'Post-Money' : 'Post-money', fmtBig(postMoney)],
[de ? 'Investor-Anteil' : 'Investor share', Math.round(equityShare * 100) + '%'],
]
const useOfFunds = funding?.use_of_funds || [
{ category: 'engineering', percentage: 45, label_de: 'Engineering & Produkt', label_en: 'Engineering & Product' },
{ category: 'sales', percentage: 30, label_de: 'Vertrieb & Marketing', label_en: 'Sales & Marketing' },
{ category: 'hardware', percentage: 10, label_de: 'Infrastruktur & Hardware', label_en: 'Infrastructure & Hardware' },
{ category: 'legal', percentage: 10, label_de: 'Legal & Compliance', label_en: 'Legal & Compliance' },
{ category: 'reserve', percentage: 5, label_de: 'Reserve', label_en: 'Reserve' },
]
return (
<Page kicker="14" section={de ? 'THE ASK' : 'THE ASK'} title={de ? `${funding?.round_name || 'Pre-Seed'}: ${formatFunding(amount)} via ${instrument}.` : `${funding?.round_name || 'Pre-Seed'}: ${formatFunding(amount)} via ${instrument.toLowerCase()}.`} subtitle={de ? '18 Monate Runway zur Profitabilität. Use of Funds in 5 Buckets. INVEST-Zuschuss 20% rückzahlbar.' : '18-month runway to profitability. Use of funds across 5 buckets. 20% INVEST grant eligible.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<div style={{ display: 'grid', gridTemplateColumns: '1.2fr 1fr', gap: '8mm', flex: 1, minHeight: 0 }}>
{/* Hero amount */}
<div style={{ display: 'flex', flexDirection: 'column' }}>
<div style={{ borderLeft: `3px solid ${COLORS.indigo600}`, paddingLeft: '5mm', marginBottom: '6mm' }}>
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.indigo600, textTransform: 'uppercase', letterSpacing: '0.12em' }}>{de ? 'Funding' : 'Funding'}</div>
<div style={{ fontSize: '54pt', fontWeight: 800, color: COLORS.slate900, lineHeight: 1, letterSpacing: '-0.03em', fontVariantNumeric: 'tabular-nums', marginTop: '2mm' }}>
{formatFunding(amount)}
</div>
<div style={{ fontSize: '10pt', color: COLORS.slate600, marginTop: '3mm' }}>{instrument} &middot; {funding?.round_name || 'Pre-Seed'} &middot; {de ? 'Zielabschluss' : 'Target close'}: {formatTargetDate(funding?.target_date, de)}</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(' + termTiles.length + ', 1fr)', gap: '4mm' }}>
{termTiles.map(([label, val], i) => (
<div key={i} style={{ border: `1px solid ${COLORS.slate200}`, padding: '3mm' }}>
<div style={{ fontSize: '7.5pt', color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.08em', fontWeight: 600 }}>{label}</div>
<div style={{ fontSize: '16pt', fontWeight: 800, color: COLORS.slate900, marginTop: '1mm', fontVariantNumeric: 'tabular-nums' }}>{val}</div>
</div>
))}
</div>
<div style={{ marginTop: 'auto' }}>
<Callout tone="accent" label={de ? 'Was wir damit erreichen' : 'What this gets us'}>
{de
? '18 Monate Runway · GmbH-Gründung · Engineering-Team auf 5 erweitert · 50100 Kunden onboarded · ARR €1,52,5M in 18 Monaten · Vorbereitung Series A.'
: '18-month runway · GmbH incorporated · engineering team scaled to 5 · 50100 customers onboarded · ARR €1.52.5M in 18 months · Series A readiness.'}
</Callout>
</div>
</div>
{/* Use of funds */}
<div>
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '3mm' }}>{de ? 'Use of Funds' : 'Use of funds'}</div>
{(() => {
const palette = [COLORS.indigo600, COLORS.indigo500, COLORS.amber600, COLORS.slate600, COLORS.slate400]
return (
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '6mm' }}>
<div style={{ flexShrink: 0 }}>
<DonutChart
size={48}
thickness={9}
segments={useOfFunds.map((u, i) => ({ label: de ? u.label_de : u.label_en, pct: u.percentage, color: palette[i % palette.length] }))}
/>
</div>
<div style={{ flex: 1, minWidth: 0 }}>
{useOfFunds.map((u, i) => (
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: '3mm', padding: '2mm 0', borderBottom: `1px solid ${COLORS.slate100}` }}>
<div style={{ width: '3mm', height: '3mm', background: palette[i % palette.length], flexShrink: 0, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
<div style={{ flex: 1, fontSize: '8.5pt', color: COLORS.slate800, fontWeight: 500 }}>{de ? u.label_de : u.label_en}</div>
<div style={{ fontSize: '11pt', fontWeight: 800, color: COLORS.slate900, fontVariantNumeric: 'tabular-nums', minWidth: '12mm', textAlign: 'right' }}>{u.percentage}%</div>
<div style={{ fontSize: '7.5pt', color: COLORS.slate500, fontVariantNumeric: 'tabular-nums', minWidth: '14mm', textAlign: 'right' }}>{(amount * u.percentage / 100 / 1000).toFixed(0)}k</div>
</div>
))}
</div>
</div>
)
})()}
</div>
</div>
</Page>
)
}
/* ===== CUSTOMER SAVINGS ===== */
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 (
<Page kicker="15" section={de ? 'KUNDENERSPARNIS' : 'CUSTOMER SAVINGS'} title={de ? 'Kunden sparen mehr als sie zahlen, vom ersten Tag an.' : 'Customers save more than they pay, from day one.'} subtitle={de ? 'Detaillierte Aufschlüsselung für ein typisches KMU (50 MA) im ersten Jahr. €25k Kosten, €55k Ersparnis.' : 'Detailed breakdown for a typical SME (50 emp.) in year one. €25k cost, €55k savings.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
{/* Big stat header */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '4mm', padding: '4mm 0', borderTop: `1px solid ${COLORS.slate200}`, borderBottom: `1px solid ${COLORS.slate200}`, marginBottom: '5mm' }}>
{[
{ 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) => (
<div key={i}>
<div style={{ fontSize: '26pt', fontWeight: 800, color: k.tone, lineHeight: 1, fontVariantNumeric: 'tabular-nums', letterSpacing: '-0.02em' }}>{k.v}</div>
<div style={{ fontSize: '7.5pt', color: COLORS.slate500, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em', marginTop: '1.5mm' }}>{k.l}</div>
</div>
))}
</div>
{/* Bar comparison */}
<div style={{ flex: 1, minHeight: 0, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8mm' }}>
<div>
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '3mm' }}>{de ? 'Heute vs. mit BreakPilot (€/Jahr/KMU)' : 'Today vs. with BreakPilot (€/yr/SME)'}</div>
<ComparisonBars
rows={items.map(it => ({
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'}
/>
</div>
<div>
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '3mm' }}>{de ? 'Ersparnis-Aufschlüsselung' : 'Savings breakdown'}</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '0' }}>
<StatLine label={de ? 'Pentests (kontinuierlich, inklusive)' : 'Pentests (continuous, included)'} value="€13.000" tone="positive" />
<StatLine label={de ? 'CE-Risiko (Code-basiert, inkl.)' : 'CE risk (code-based, incl.)'} value="€9.000" tone="positive" />
<StatLine label={de ? 'Compliance-Zeit (80%)' : 'Compliance time (80%)'} value="€15.000" tone="positive" />
<StatLine label={de ? 'Audit-Vorber. (auf Knopfdruck)' : 'Audit prep (one-click)'} value="€9.000" tone="positive" />
<StatLine label={de ? 'Legal-Stunden (60%)' : 'Legal hours (60%)'} value="€5.000" tone="positive" />
<StatLine label={de ? 'Schulungen (Academy inkl.)' : 'Training (Academy incl.)'} value="€4.000" tone="positive" />
</div>
<div style={{ marginTop: '5mm' }}>
<Callout tone="negative" label={de ? 'Versteckte Kosten' : 'Hidden costs'}>
{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.'}
</Callout>
</div>
</div>
</div>
</Page>
)
}
@@ -0,0 +1,302 @@
import { Language, FMResult } from '@/lib/types'
import { Page, COLORS, Bullets } from './PrintLayout'
import {
ScanLine, Shield, Database, Brain, ShieldCheck, Lock, MessageSquare, Wrench,
Layers, Sparkles, TrendingUp, Globe,
type LucideIcon,
} from 'lucide-react'
import { getDetails } from '@/components/slides/USPSlide.data'
import { computeAnnualKPIs } from '@/lib/finanzplan/annual-kpis'
interface SlideBase { lang: Language; pageNum: number; totalPages: number; versionName: string }
const MONO = "'JetBrains Mono', ui-monospace, monospace"
/* ====================================================================== */
/* TL;DR — 02 · 30 Sekunden (4 quad cards) */
/* ====================================================================== */
export function PrintTLDRPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
const de = lang === 'de'
const cards = de ? [
{ kicker: '01 · Scale', title: '25.000+ Controls', body: 'Atomare Prüfaspekte über DSGVO, AI Act, NIS-2, CRA, MaschVO und 380+ weitere Quellen.', ticker: 'idx 26.123 atomic checks', tint: COLORS.violet600 },
{ kicker: '02 · Sovereignty', title: '100 % EU-souverän', body: 'BSI-C5-zertifizierte Cloud in Deutschland und Frankreich. Keine US-Anbieter. Air-gap-fähig.', ticker: 'region BSI C5 · DE/FR', tint: COLORS.violet500 },
{ kicker: '03 · Bidirectional', title: 'Compliance ↔ Code', body: 'Policy-Änderungen fliessen in den Code; Code-Änderungen aktualisieren Policies. Zero Drift.', ticker: 'sync policy.md → controller.ts', tint: COLORS.amber600 },
{ kicker: '04 · Speed', title: '<20 Tage audit-ready', body: 'Vom Vertrag zum auditfähigen Status: typischerweise 1420 Tage. White-Glove-Onboarding.', ticker: 'tag 17 bis audit-ready', tint: COLORS.amber700 },
] : [
{ kicker: '01 · Scale', title: '25,000+ Controls', body: 'Atomic audit aspects across GDPR, AI Act, NIS-2, CRA, Machinery Reg. and 380+ other sources.', ticker: 'idx 26,123 atomic checks', tint: COLORS.violet600 },
{ kicker: '02 · Sovereignty', title: '100 % EU-sovereign', body: 'BSI-C5 certified cloud in Germany and France. No US vendors. Air-gap capable.', ticker: 'region BSI C5 · DE/FR', tint: COLORS.violet500 },
{ kicker: '03 · Bidirectional', title: 'Compliance ↔ Code', body: 'Policy edits flow into code; code changes update policies. Zero drift.', ticker: 'sync policy.md → controller.ts', tint: COLORS.amber600 },
{ kicker: '04 · Speed', title: '<20 days audit-ready', body: 'From contract to audit-ready: typically 1420 days. White-glove onboarding.', ticker: 'day 17 to audit-ready', tint: COLORS.amber700 },
]
return (
<Page kicker="02" section={de ? '30 SEKUNDEN' : '30 SECONDS'} title={de ? 'Was BreakPilot in einem Satz.' : 'BreakPilot in one sentence.'} subtitle={de ? 'Kontinuierliche Compliance & Security für den industriellen Mittelstand — EU-souverän, Bidirektional, in unter 20 Tagen produktiv.' : 'Continuous compliance & security for the industrial mid-market — EU-sovereign, bidirectional, productive in under 20 days.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gridTemplateRows: '1fr 1fr', gap: '5mm', flex: 1, minHeight: 0 }}>
{cards.map((c, i) => (
<div key={i} style={{ border: `1px solid ${c.tint}55`, borderTop: `3px solid ${c.tint}`, borderRadius: '4pt', background: `linear-gradient(135deg, ${c.tint}10 0%, #ffffff 100%)`, padding: '5mm 6mm', display: 'flex', flexDirection: 'column', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<div style={{ fontFamily: MONO, fontSize: '8pt', fontWeight: 700, color: c.tint, textTransform: 'uppercase', letterSpacing: '0.22em', marginBottom: '3mm' }}>{c.kicker}</div>
<div style={{ fontSize: '22pt', fontWeight: 800, color: COLORS.slate900, lineHeight: 1.05, letterSpacing: '-0.025em', marginBottom: '3mm' }}>{c.title}</div>
<div style={{ fontSize: '9pt', color: COLORS.slate700, lineHeight: 1.5, flex: 1 }}>{c.body}</div>
<div style={{ marginTop: '3mm', paddingTop: '2mm', borderTop: `1px solid ${COLORS.slate200}`, display: 'flex', alignItems: 'center', gap: '2mm' }}>
<span style={{ width: '2mm', height: '2mm', borderRadius: '50%', background: COLORS.emerald600, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
<span style={{ fontFamily: MONO, fontSize: '7pt', color: c.tint, fontWeight: 700 }}>{c.ticker}</span>
</div>
</div>
))}
</div>
</Page>
)
}
/* ====================================================================== */
/* DIFFERENTIATORS — 4 under-the-hood cards (moved out of USP p2) */
/* ====================================================================== */
const DIFF_ICON: Record<string, LucideIcon> = {
trace: Layers,
engine: Sparkles,
opt: TrendingUp,
stack: Globe,
}
export function PrintDifferentiatorsPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
const de = lang === 'de'
const d = getDetails(de)
const cards = ['trace', 'engine', 'opt', 'stack'] as const
const tickers = de
? ['trace 13.605 evidence-chain', 'validate 1.450 · 98,9 %', 'optimize gap → policy §4.2', 'check BSI C5 · 1.382']
: ['trace 13,605 evidence-chain', 'validate 1,450 · 98.9 %', 'optimize gap → policy §4.2', 'check BSI C5 · 1,382']
return (
<Page kicker="06" section={de ? 'DIFFERENTIATORS' : 'DIFFERENTIATORS'} title={de ? 'Vier technische Differentiator.' : 'Four technical differentiators.'} subtitle={de ? 'Was kein anderer Anbieter geschlossen liefert: Traceability, kontinuierliche Engine, Compliance-Optimizer, EU-Trust-Stack.' : 'What no other vendor delivers end-to-end: traceability, continuous engine, compliance optimizer, EU-trust stack.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '4mm', flex: 1, minHeight: 0 }}>
{cards.map((k, i) => {
const p = d[k]
const Icon = DIFF_ICON[k]
const tint = i < 2 ? COLORS.violet600 : COLORS.amber600
const tintDark = i < 2 ? COLORS.violet700 : COLORS.amber700
const tintLight = i < 2 ? COLORS.violet50 : COLORS.amber50
return (
<div key={k} style={{ border: `1px solid ${tint}40`, background: `linear-gradient(135deg, ${tintLight} 0%, #ffffff 100%)`, borderRadius: '4pt', padding: '4mm 5mm', display: 'flex', flexDirection: 'column', overflow: 'hidden', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<div style={{ width: '9mm', height: '9mm', background: '#ffffff', borderRadius: '2pt', border: `1px solid ${tint}`, display: 'flex', alignItems: 'center', justifyContent: 'center', color: tint, marginBottom: '3mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
{Icon && <Icon size={16} strokeWidth={1.5} />}
</div>
<div style={{ fontFamily: MONO, fontSize: '7pt', fontWeight: 700, color: tintDark, textTransform: 'uppercase', letterSpacing: '0.16em', marginBottom: '1mm' }}>{String(i + 1).padStart(2, '0')} &middot; {p.kicker.replace(/^Säule[\s·]+|^Under the Hood|^Pillar[\s·]+/i, '').trim() || p.kicker}</div>
<div style={{ fontSize: '12pt', fontWeight: 700, color: COLORS.slate900, lineHeight: 1.15, marginBottom: '2.5mm', letterSpacing: '-0.005em' }}>{p.title}</div>
<div style={{ fontSize: '8pt', color: COLORS.slate700, lineHeight: 1.45, marginBottom: '2.5mm' }}>{p.body}</div>
{p.bullets && <Bullets dense items={p.bullets} />}
<div style={{ marginTop: 'auto', paddingTop: '2mm', borderTop: `1px solid ${COLORS.slate200}`, display: 'flex', alignItems: 'center', gap: '2mm' }}>
<span style={{ width: '2mm', height: '2mm', borderRadius: '50%', background: COLORS.emerald600, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
<span style={{ fontFamily: MONO, fontSize: '6.5pt', color: tintDark, fontWeight: 700 }}>{tickers[i]}</span>
</div>
</div>
)
})}
</div>
</Page>
)
}
/* ====================================================================== */
/* KPI HERO — 8 trajectory tiles 2026 → 2030 */
/* ====================================================================== */
function fmtEur(n: number): string {
const abs = Math.abs(n)
const sign = n < 0 ? '' : ''
if (abs >= 1e6) return `${sign}${(abs / 1e6).toLocaleString('de-DE', { maximumFractionDigits: 1 })}M`
if (abs >= 1e3) return `${sign}${Math.round(abs / 1e3)}k`
return `${sign}${Math.round(abs)}`
}
export function PrintKPIHeroPage({ fmResults, lang, pageNum, totalPages, versionName }: SlideBase & { fmResults: FMResult[] }) {
const de = lang === 'de'
const kpis = computeAnnualKPIs(fmResults)
const k26 = kpis.find(k => k.year === 2026)
const k30 = kpis.find(k => k.year === 2030)
const breakEvenYear = kpis.find(k => k.ebit > 0)?.year
const tiles = k26 && k30 ? [
{ label: 'ARR', start: fmtEur(k26.arr), end: fmtEur(k30.arr), endColor: COLORS.violet600 },
{ label: de ? 'Kunden' : 'Customers', start: k26.customers.toLocaleString('de-DE'), end: k30.customers.toLocaleString('de-DE'), endColor: COLORS.violet600 },
{ label: de ? 'ARPU / Mo' : 'ARPU / mo', start: fmtEur(k26.arpu), end: fmtEur(k30.arpu), endColor: COLORS.slate900 },
{ label: de ? 'Mitarbeiter' : 'Employees', start: String(k26.employees), end: String(k30.employees), endColor: COLORS.slate900 },
{ label: de ? 'Bruttomarge' : 'Gross margin', start: `${k26.grossMargin}%`, end: `${k30.grossMargin}%`, endColor: COLORS.emerald700 },
{ label: 'EBIT', start: fmtEur(k26.ebit), end: fmtEur(k30.ebit), endColor: k30.ebit >= 0 ? COLORS.emerald700 : COLORS.red700 },
{ label: de ? 'Netto-Ergebnis' : 'Net income', start: fmtEur(k26.netIncome), end: fmtEur(k30.netIncome), endColor: k30.netIncome >= 0 ? COLORS.emerald700 : COLORS.red700 },
{ label: de ? 'Cash (Dez)' : 'Cash (Dec)', start: fmtEur(k26.cashBalance), end: fmtEur(k30.cashBalance), endColor: COLORS.emerald700 },
] : []
return (
<Page kicker="18" section={de ? 'ANHANG · KENNZAHLEN' : 'APPENDIX · KEY METRICS'} title={de ? 'Trajektorie 2026 → 2030.' : 'Trajectory 2026 → 2030.'} subtitle={de ? `Acht investorrelevante KPIs auf einen Blick. Base-Case-Szenario, abgeleitet aus dem Finanzplan. Break-Even: ${breakEvenYear ?? 'Q3 2029'}.` : `Eight investor-relevant KPIs at a glance. Base-case scenario, derived from the financial plan. Break-even: ${breakEvenYear ?? 'Q3 2029'}.`} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
{tiles.length === 0 ? (
<p style={{ fontSize: '10pt', color: COLORS.slate600 }}>{de ? 'Keine Finanzdaten verfügbar.' : 'No financial data available.'}</p>
) : (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gridTemplateRows: '1fr 1fr', gap: '5mm', flex: 1, minHeight: 0 }}>
{tiles.map((t, i) => (
<div key={i} style={{ border: `1px solid ${COLORS.slate200}`, borderTop: `3px solid ${COLORS.violet600}`, background: '#ffffff', padding: '5mm', display: 'flex', flexDirection: 'column', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<div style={{ fontFamily: MONO, fontSize: '7.5pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.18em', marginBottom: '4mm' }}>{t.label}</div>
<div style={{ display: 'flex', alignItems: 'baseline', gap: '3mm', marginBottom: 'auto' }}>
<span style={{ fontSize: '11pt', fontWeight: 600, color: COLORS.slate400, fontVariantNumeric: 'tabular-nums' }}>{t.start}</span>
<span style={{ fontFamily: MONO, fontSize: '11pt', color: COLORS.slate400, fontWeight: 700 }}></span>
<span style={{ fontSize: '24pt', fontWeight: 800, color: t.endColor, lineHeight: 1, fontVariantNumeric: 'tabular-nums', letterSpacing: '-0.025em' }}>{t.end}</span>
</div>
<div style={{ marginTop: '3mm', paddingTop: '2mm', borderTop: `1px solid ${COLORS.slate100}`, fontFamily: MONO, fontSize: '6.5pt', color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.12em', fontWeight: 700 }}>2026 &middot; 2030</div>
</div>
))}
</div>
)}
</Page>
)
}
/* ====================================================================== */
/* TECH STACK — 8-category grid */
/* ====================================================================== */
export function PrintTechStackPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
const de = lang === 'de'
const cats = de ? [
{ name: 'Frontend', icon: ScanLine, items: ['Next.js 15', 'React 19', 'Tailwind CSS', 'Framer Motion', 'Dioxus (Rust)'] },
{ name: 'Backend', icon: Wrench, items: ['Go/Gin', 'Python/FastAPI', 'Rust/Axum', 'OpenAPI'] },
{ name: 'Storage', icon: Database, items: ['PostgreSQL 16', 'MongoDB', 'Qdrant Vector DB', 'Valkey (cache)'] },
{ name: 'KI / RAG', icon: Brain, items: ['LiteLLM', 'Qwen3-32B', 'DeepSeek-R1', 'Sentence-Transformers', 'LangGraph'] },
{ name: 'Code-Scanning', icon: Shield, items: ['Semgrep', 'Gitleaks', 'Syft', 'Trivy', 'CycloneDX'] },
{ name: 'Auth & SSO', icon: Lock, items: ['Keycloak', 'OIDC', 'OPA (policies)'] },
{ name: 'Kommunikation', icon: MessageSquare, items: ['Matrix (chat)', 'Jitsi (video)', 'Mailpit'] },
{ name: 'DevOps', icon: ShieldCheck, items: ['Gitea', 'Woodpecker CI', 'HashiCorp Vault', 'Orca', 'Docker Compose'] },
] : [
{ name: 'Frontend', icon: ScanLine, items: ['Next.js 15', 'React 19', 'Tailwind CSS', 'Framer Motion', 'Dioxus (Rust)'] },
{ name: 'Backend', icon: Wrench, items: ['Go/Gin', 'Python/FastAPI', 'Rust/Axum', 'OpenAPI'] },
{ name: 'Storage', icon: Database, items: ['PostgreSQL 16', 'MongoDB', 'Qdrant vector DB', 'Valkey (cache)'] },
{ name: 'AI / RAG', icon: Brain, items: ['LiteLLM', 'Qwen3-32B', 'DeepSeek-R1', 'Sentence-Transformers', 'LangGraph'] },
{ name: 'Code scanning', icon: Shield, items: ['Semgrep', 'Gitleaks', 'Syft', 'Trivy', 'CycloneDX'] },
{ name: 'Auth & SSO', icon: Lock, items: ['Keycloak', 'OIDC', 'OPA (policies)'] },
{ name: 'Communication', icon: MessageSquare, items: ['Matrix (chat)', 'Jitsi (video)', 'Mailpit'] },
{ name: 'DevOps', icon: ShieldCheck, items: ['Gitea', 'Woodpecker CI', 'HashiCorp Vault', 'Orca', 'Docker Compose'] },
]
return (
<Page kicker="26" section={de ? 'ANHANG · TECH-STACK' : 'APPENDIX · TECH STACK'} title={de ? '8 Kategorien. Polyglott. 100 % Open Source.' : '8 categories. Polyglot. 100 % open source.'} subtitle={de ? 'Alle Komponenten mit kommerziell nutzbarer Lizenz (MIT, Apache-2.0, BSD, ISC, MPL-2.0, LGPL). Keine GPL/AGPL. Keine US-SaaS-Abhängigkeit.' : 'All components carry a commercially usable license (MIT, Apache-2.0, BSD, ISC, MPL-2.0, LGPL). No GPL/AGPL. No US SaaS dependency.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gridTemplateRows: '1fr 1fr', gap: '4mm', flex: 1, minHeight: 0 }}>
{cats.map((c, i) => {
const Icon = c.icon
return (
<div key={i} style={{ border: `1px solid ${COLORS.slate200}`, borderTop: `2px solid ${COLORS.violet600}`, background: '#ffffff', padding: '4mm 5mm', display: 'flex', flexDirection: 'column', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '3mm', marginBottom: '3mm' }}>
<div style={{ width: '8mm', height: '8mm', background: COLORS.violet50, borderRadius: '2pt', display: 'flex', alignItems: 'center', justifyContent: 'center', color: COLORS.violet600, flexShrink: 0, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<Icon size={15} strokeWidth={1.5} />
</div>
<div style={{ fontSize: '11pt', fontWeight: 700, color: COLORS.slate900, lineHeight: 1.15 }}>{c.name}</div>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '0' }}>
{c.items.map((it, j) => (
<div key={j} style={{ fontFamily: MONO, fontSize: '8pt', color: COLORS.slate700, padding: '1.2mm 0', borderTop: j > 0 ? `1px solid ${COLORS.slate100}` : 'none', lineHeight: 1.3 }}>{it}</div>
))}
</div>
</div>
)
})}
</div>
</Page>
)
}
/* ====================================================================== */
/* ANHANG DIVIDER (moved from PrintAnnexSlides.tsx) */
/* ====================================================================== */
export function PrintAnnexDividerPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
const de = lang === 'de'
const sections: [string, string, string][] = de ? [
['18', 'Go-to-Market Strategie', 'Pilot · Skalierung · Expansion'],
['19', 'Kennzahlen 2026 → 2030', '8 KPIs Trajektorie · Base-Case'],
['20', 'Finanzplan', 'P&L 20262030 · KPI-Dashboard'],
['21', 'P&L Detail', 'Annualisierte Gewinn-/Verlust-Rechnung'],
['22', 'Treibervariablen', 'Annahmen + Sensitivitätsszenarien'],
['23', 'Regulatorische Details', 'DSGVO · AI Act · NIS-2 · CRA · MaschVO'],
['24', 'Systemarchitektur', '3 Tiers · LiteLLM Gateway · lokale Inferenz'],
['25', 'Engineering Deep Dive', '500K+ LoC · 45 Container · 100 % Self-Hosted'],
['26', 'Tech-Stack', '8 Kategorien · polyglott · Open Source'],
['27', 'KI-Pipeline', 'RAG · Multi-Agent · Document Intelligence · QA'],
['28', 'Risiken & Mitigation', '10 Risiken in 5 Kategorien'],
['29', 'Glossar', '30 Begriffe · Compliance · Engineering · Recht'],
] : [
['18', 'Go-to-Market Strategy', 'Pilot · Scale · Expansion'],
['19', 'KPIs 2026 → 2030', '8 KPI trajectory · base case'],
['20', 'Financial Plan', 'P&L 20262030 · KPI dashboard'],
['21', 'P&L Detail', 'Annualized profit & loss'],
['22', 'Driver Variables', 'Assumptions + sensitivity scenarios'],
['23', 'Regulatory Details', 'GDPR · AI Act · NIS-2 · CRA · Machinery Reg.'],
['24', 'System Architecture', '3 tiers · LiteLLM gateway · local inference'],
['25', 'Engineering Deep Dive', '500K+ LoC · 45 containers · 100 % self-hosted'],
['26', 'Tech Stack', '8 categories · polyglot · open source'],
['27', 'AI Pipeline', 'RAG · multi-agent · document intelligence · QA'],
['28', 'Risks & Mitigation', '10 risks across 5 categories'],
['29', 'Glossary', '30 terms · compliance · engineering · law'],
]
return (
<div className="print-page-break">
<div className="print-page print-page-bg" style={{
width: '297mm', height: '210mm', color: COLORS.slate900,
fontFamily: "'Inter', system-ui, sans-serif", boxSizing: 'border-box',
padding: '14mm 18mm', margin: '0 auto 24px',
boxShadow: '0 4px 24px rgba(59,26,122,0.10)', overflow: 'hidden',
WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact',
display: 'flex', flexDirection: 'column',
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderBottom: `1px solid ${COLORS.slate200}`, paddingBottom: '3mm' }}>
<span style={{ fontFamily: MONO, fontSize: '7.5pt', fontWeight: 700, color: COLORS.violet600, textTransform: 'uppercase', letterSpacing: '0.22em' }}>{de ? 'Teil II · Anhang' : 'Part II · Appendix'}</span>
<span style={{ fontFamily: MONO, fontSize: '7pt', color: COLORS.slate500, letterSpacing: '0.16em', textTransform: 'uppercase', fontWeight: 700 }}>BreakPilot &middot; ComplAI</span>
</div>
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center', maxWidth: '260mm' }}>
<div style={{ fontFamily: MONO, fontSize: '10pt', fontWeight: 700, color: COLORS.violet600, textTransform: 'uppercase', letterSpacing: '0.3em', marginBottom: '5mm' }}>
{de ? '17 · Kapitelwechsel' : '17 · Chapter break'}
</div>
<h1 style={{ fontSize: '64pt', fontWeight: 800, color: COLORS.slate900, lineHeight: 0.95, letterSpacing: '-0.035em', margin: 0 }}>
{de ? 'Anhang' : 'Appendix'}<span style={{ color: COLORS.violet600 }}>.</span>
</h1>
<div style={{ height: '3px', width: '60mm', background: `linear-gradient(90deg, ${COLORS.violet700} 0%, ${COLORS.violet400} 50%, ${COLORS.violet700} 100%)`, marginTop: '5mm', marginBottom: '6mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
<p style={{ fontSize: '13pt', fontWeight: 500, color: COLORS.slate700, lineHeight: 1.3, margin: 0, letterSpacing: '-0.008em', maxWidth: '230mm' }}>
{de
? 'Detailangaben & Belege. Was wir in der Pitch gesagt haben, mit Quellen, Zahlen und Architektur belegt.'
: 'Detail & evidence. Everything we claimed in the pitch, backed by sources, numbers and architecture.'}
</p>
<div style={{ marginTop: '8mm' }}>
<div style={{ fontFamily: MONO, fontSize: '8pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.18em', marginBottom: '4mm' }}>
{de ? 'Auf den folgenden Seiten' : 'On the following pages'}
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '4mm 8mm' }}>
{sections.map(([n, t, sub]) => (
<div key={n} style={{ display: 'flex', alignItems: 'flex-start', gap: '4mm' }}>
<span style={{ fontFamily: MONO, fontSize: '13pt', fontWeight: 800, color: COLORS.violet600, lineHeight: 1, fontVariantNumeric: 'tabular-nums', letterSpacing: '-0.02em', minWidth: '11mm' }}>{n}</span>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: '9.5pt', fontWeight: 700, color: COLORS.slate900, lineHeight: 1.2 }}>{t}</div>
<div style={{ fontSize: '7pt', color: COLORS.slate600, marginTop: '0.5mm', lineHeight: 1.35 }}>{sub}</div>
</div>
</div>
))}
</div>
</div>
</div>
<div className="print-mono" style={{ fontFamily: MONO, paddingTop: '3mm', borderTop: `1px solid ${COLORS.slate200}`, display: 'flex', alignItems: 'center', justifyContent: 'space-between', fontSize: '7pt', color: COLORS.slate500, letterSpacing: '0.16em', textTransform: 'uppercase', fontWeight: 700 }}>
<span>BreakPilot &middot; ComplAI</span>
<span style={{ color: COLORS.violet600 }}>{versionName}</span>
<span style={{ fontVariantNumeric: 'tabular-nums' }}>{String(pageNum).padStart(2, '0')} / {String(totalPages).padStart(2, '0')}</span>
</div>
</div>
</div>
)
}
@@ -0,0 +1,465 @@
import { Language, PitchProduct } from '@/lib/types'
import { Page, Bullets, Callout, COLORS, DataTable } from './PrintLayout'
import { 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<string, LucideIcon> = {
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) {
const de = lang === 'de'
const d = getDetails(de)
const pillars = ['rfq', 'process', 'bidir', 'cont'] as const
return (
<Page kicker="05" section={de ? 'USP · 1 / 2' : 'USP · 1 / 2'} title={de ? 'Compliance ↔ Code, immer in Sync.' : 'Compliance ↔ Code, always in sync.'} subtitle={de ? 'Vier Säulen, die kein anderer Anbieter geschlossen liefert: RFQ-Prüfung, Prozess-Compliance, bidirektionale Sync, kontinuierliche Engine.' : 'Four pillars no other vendor delivers end-to-end: RFQ verification, process compliance, bidirectional sync, continuous engine.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '5mm', flex: 1, minHeight: 0 }}>
{pillars.map((k, i) => {
const p = d[k]
const Icon = USP_ICON[k]
return (
<div key={k} style={{ border: `1px solid ${COLORS.slate200}`, borderTop: `3px solid ${COLORS.violet600}`, padding: '3mm 4mm', display: 'flex', flexDirection: 'column', minHeight: 0, overflow: 'hidden', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
{/* Header: icon + title inline, kicker top-right */}
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '3mm', marginBottom: '2mm' }}>
<div style={{ width: '9mm', height: '9mm', flexShrink: 0, background: COLORS.violet50, display: 'flex', alignItems: 'center', justifyContent: 'center', color: COLORS.violet600, borderRadius: '2pt', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
{Icon && <Icon size={18} strokeWidth={1.5} />}
</div>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: '2mm' }}>
<span style={{ fontFamily: "'JetBrains Mono', ui-monospace, monospace", fontSize: '6.5pt', fontWeight: 700, color: COLORS.violet600, textTransform: 'uppercase', letterSpacing: '0.16em' }}>{p.kicker}</span>
<span style={{ fontFamily: "'JetBrains Mono', ui-monospace, monospace", fontSize: '6.5pt', color: COLORS.slate400, fontVariantNumeric: 'tabular-nums' }}>{String(i + 1).padStart(2, '0')} / 04</span>
</div>
<div style={{ fontSize: '12pt', fontWeight: 700, color: COLORS.slate900, lineHeight: 1.15, letterSpacing: '-0.005em', marginTop: '1mm' }}>{p.title}</div>
</div>
</div>
<div style={{ fontSize: '8pt', color: COLORS.slate700, lineHeight: 1.45, marginBottom: '2mm' }}>{p.body}</div>
{p.bullets && <Bullets dense items={p.bullets} />}
{p.stat && (
<div style={{ marginTop: 'auto', paddingTop: '2mm', borderTop: `1px solid ${COLORS.slate200}`, display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: '3mm' }}>
<span style={{ fontFamily: "'JetBrains Mono', ui-monospace, monospace", fontSize: '6.5pt', color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.12em', fontWeight: 700 }}>{p.stat.k}</span>
<span style={{ fontSize: '11pt', fontWeight: 800, color: COLORS.emerald700, fontVariantNumeric: 'tabular-nums', whiteSpace: 'nowrap' }}>{p.stat.v}</span>
</div>
)}
</div>
)
})}
</div>
</Page>
)
}
/* ===== USP, PAGE 2 just the closing loop (was: 4 cards + loop) =====
*
* The 4 under-the-hood cards moved to the dedicated Differentiators slide
* (PrintDifferentiatorsPage). This page is now a hero "Compliance Code
* always in sync" closing card for the USP block.
*/
export function PrintUSPPage2({ lang, pageNum, totalPages, versionName }: SlideBase) {
const de = lang === 'de'
const d = getDetails(de)
const MONO = "'JetBrains Mono', ui-monospace, monospace"
return (
<Page kicker="05" section={de ? 'USP · 2 / 2' : 'USP · 2 / 2'} title={de ? 'Compliance ↔ Code, immer in Sync.' : 'Compliance ↔ Code, always in sync.'} subtitle={de ? 'Eine geschlossene Schleife: jede Policy-Änderung fliesst in den Code; jede Code-Änderung in die Policy zurück. Zero Drift, eine Quelle der Wahrheit.' : 'A closed loop: every policy change flows into code; every code change flows back into policy. Zero drift, one source of truth.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
{/* Full-page hero loop diagram */}
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
<div style={{ border: `1px solid ${COLORS.violet300}`, background: `linear-gradient(135deg, ${COLORS.violet50} 0%, #ffffff 50%, ${COLORS.violet50} 100%)`, borderRadius: '6pt', padding: '12mm 14mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '4mm', marginBottom: '6mm' }}>
<div style={{ width: '12mm', height: '12mm', borderRadius: '50%', background: COLORS.violet600, color: '#ffffff', display: 'flex', alignItems: 'center', justifyContent: 'center', boxShadow: `0 0 0 4px ${COLORS.violet50}`, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<Infinity size={22} strokeWidth={2} />
</div>
<span style={{ fontFamily: MONO, fontSize: '9pt', fontWeight: 700, color: COLORS.violet700, textTransform: 'uppercase', letterSpacing: '0.24em' }}>{de ? 'Die Schleife · Always in Sync' : 'The Loop · Always in Sync'}</span>
</div>
<div style={{ fontSize: '18pt', fontWeight: 800, color: COLORS.slate900, marginBottom: '4mm', lineHeight: 1.15, letterSpacing: '-0.01em', maxWidth: '210mm' }}>{d.hub.title}</div>
<div style={{ fontSize: '10pt', color: COLORS.slate700, lineHeight: 1.55, marginBottom: '6mm', maxWidth: '220mm' }}>{d.hub.body}</div>
<div style={{ background: '#ffffff', border: `1px solid ${COLORS.violet200}`, borderRadius: '3pt', padding: '5mm 6mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<LoopDiagram lang={lang} />
</div>
{d.hub.bullets && (
<div style={{ marginTop: '5mm' }}>
<Bullets dense tone="accent" items={d.hub.bullets} />
</div>
)}
</div>
</div>
</Page>
)
}
/* ===== REGULATORY LANDSCAPE ===== */
const RL_CATEGORIES_DE = [
{ name: 'Datenschutz', sample: 'DSGVO · ePrivacy · TTDSG · BDSG', count: 32 },
{ name: 'Cybersicherheit', sample: 'NIS2 · IT-SiG · BSIG · KRITIS-Verordnung', count: 47 },
{ name: 'KI-Regulierung', sample: 'AI Act · KI-Haftungsrichtlinie', count: 18 },
{ name: 'Digitale Märkte', sample: 'DMA · DSA · Data Act · Data Governance Act', count: 24 },
{ name: 'Produktsicherheit', sample: 'CRA · Maschinenverordnung · Produktsicherheitsgesetz', count: 41 },
{ name: 'Finanzregulierung', sample: 'DORA · MiCA · FinmadiG · KWG', count: 53 },
{ name: 'Gesundheitsdaten', sample: 'MDR · IVDR · PatDG · Krankenhausgesetz', count: 28 },
{ name: 'Verbraucherschutz', sample: 'UWG · BGB · Geschäftsgeheimnisschutz · HinSchG', count: 36 },
]
const RL_CATEGORIES_EN = [
{ name: 'Data Privacy', sample: 'GDPR · ePrivacy · TTDSG · BDSG', count: 32 },
{ name: 'Cybersecurity', sample: 'NIS2 · IT-SecAct · BSIG · KRITIS', count: 47 },
{ name: 'AI Regulation', sample: 'AI Act · AI Liability Directive', count: 18 },
{ name: 'Digital Markets', sample: 'DMA · DSA · Data Act · DGA', count: 24 },
{ name: 'Product Safety', sample: 'CRA · Machinery Reg. · ProdSG', count: 41 },
{ name: 'Financial Reg.', sample: 'DORA · MiCA · FinmadiG · KWG', count: 53 },
{ name: 'Health Data', sample: 'MDR · IVDR · PatDG · Hospital Act', count: 28 },
{ name: 'Consumer Prot.', sample: 'UWG · BGB · Trade Secrets · HinSchG', count: 36 },
]
const INDUSTRIES_DE = ['Alle Unternehmen', 'Maschinenbau', 'Gesundheit', 'Finanzsektor', 'E-Commerce', 'Technologie', 'IoT / Hardware', 'KI-Anbieter', 'Krit. Infrastruktur', 'Medien', 'Öffentl. Sektor']
const INDUSTRIES_EN = ['All companies', 'Manufacturing', 'Healthcare', 'Finance', 'E-Commerce', 'Technology', 'IoT / Hardware', 'AI Providers', 'Critical Infra.', 'Media', 'Public Sector']
export function PrintRegulatoryLandscapePage({ lang, pageNum, totalPages, versionName }: SlideBase) {
const de = lang === 'de'
const cats = de ? RL_CATEGORIES_DE : RL_CATEGORIES_EN
const industries = de ? INDUSTRIES_DE : INDUSTRIES_EN
return (
<Page kicker="06" section={de ? 'REGULATORISCHE LANDSCHAFT' : 'REGULATORY LANDSCAPE'} title={de ? '380+ Originaldokumente. 10 Branchen. Eine Plattform.' : '380+ original documents. 10 industries. One platform.'} subtitle={de ? 'Vollständiger RAG-Index aller relevanten EU- und DACH-Regulierungen, kontinuierlich aktualisiert, semantisch durchsuchbar.' : 'Complete RAG index of all relevant EU and DACH regulations, continuously updated, semantically searchable.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
{/* KPI strip */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '4mm', padding: '4mm 0', borderTop: `1px solid ${COLORS.slate200}`, borderBottom: `1px solid ${COLORS.slate200}` }}>
{[
{ n: '380+', l: de ? 'Originaldokumente' : 'Original documents' },
{ n: '25.000+', l: de ? 'Extrahierte Controls' : 'Extracted controls' },
{ n: '8', l: de ? 'Regulierungs-Kategorien' : 'Regulatory categories' },
{ n: '10', l: de ? 'Branchen-Profile' : 'Industry profiles' },
].map((k, i) => (
<div key={i}>
<div style={{ fontSize: '22pt', fontWeight: 800, color: COLORS.indigo600, lineHeight: 1, fontVariantNumeric: 'tabular-nums' }}>{k.n}</div>
<div style={{ fontSize: '7.5pt', color: COLORS.slate500, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.08em', marginTop: '1.5mm' }}>{k.l}</div>
</div>
))}
</div>
<div style={{ marginTop: '5mm', flex: 1, minHeight: 0, display: 'grid', gridTemplateColumns: '1.5fr 1fr', gap: '6mm' }}>
{/* Categories */}
<div>
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '2mm' }}>{de ? 'Acht Regulierungs-Kategorien' : 'Eight regulatory categories'}</div>
<DataTable
cols={[
{ header: de ? 'Kategorie' : 'Category', width: '32%' },
{ header: de ? 'Beispiele' : 'Examples' },
{ header: '#', width: '12%', numeric: true },
]}
rows={cats.map(c => [c.name, c.sample, c.count])}
dense
highlightFirstCol
/>
</div>
{/* Industries */}
<div>
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '2mm' }}>{de ? 'Zehn Branchen-Profile' : 'Ten industry profiles'}</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '2mm' }}>
{industries.map((ind, i) => (
<div key={i} style={{ border: `1px solid ${COLORS.slate200}`, padding: '2.5mm 3mm', fontSize: '8.5pt', color: COLORS.slate800, fontWeight: i === 1 ? 700 : 500 }}>
{i === 1 && <span style={{ fontSize: '6.5pt', color: COLORS.indigo600, textTransform: 'uppercase', letterSpacing: '0.1em', fontWeight: 700, display: 'block', marginBottom: '1mm' }}>{de ? 'Kernfokus' : 'Core focus'}</span>}
{ind}
</div>
))}
</div>
</div>
</div>
<div style={{ marginTop: '4mm', flexShrink: 0 }}>
<Callout tone="accent" label={de ? 'Was das bedeutet' : 'What this means'}>
{de
? 'Ein RAG-Index für alle EU- und DACH-Regulierungen, semantisch durchsuchbar, kontinuierlich aktualisiert. Kunden fragen einmal, die Plattform antwortet aus allen Gesetzen gleichzeitig.'
: 'One RAG index for all EU and DACH regulations, semantically searchable, continuously updated. Customers ask once, the platform answers from all laws simultaneously.'}
</Callout>
</div>
</Page>
)
}
/* ===== 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', 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', 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[] }) {
void products
const de = lang === 'de'
const modules = de ? MODULES_FULL_DE : MODULES_FULL_EN
return (
<Page kicker="07" section={de ? 'MODULARER BAUKASTEN' : 'MODULAR TOOLKIT'} title={de ? '12 Module, einzeln oder als Bundle.' : '12 modules, individual or bundled.'} subtitle={de ? 'Kunden wählen die Module, die sie brauchen. Jedes Modul liefert eigene Compliance-Werte; die Plattform integriert sie zu einem Audit-Trail.' : 'Customers pick the modules they need. Each module delivers compliance value on its own; the platform unifies them into a single audit trail.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '4mm', flex: 1, minHeight: 0 }}>
{modules.map((m, i) => {
const Icon = MODULE_ICONS[i]
return (
<div key={i} style={{ border: `1px solid ${COLORS.slate200}`, borderTop: `2px solid ${COLORS.indigo600}`, padding: '3mm 4mm', display: 'flex', flexDirection: 'column', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '3mm', marginBottom: '1.5mm' }}>
<div style={{ width: '8mm', height: '8mm', background: COLORS.indigo50, display: 'flex', alignItems: 'center', justifyContent: 'center', color: COLORS.indigo600, flexShrink: 0, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<Icon size={16} strokeWidth={1.5} />
</div>
<div style={{ flex: 1, minWidth: 0, fontSize: '10pt', fontWeight: 700, color: COLORS.slate900, lineHeight: 1.15 }}>{m.name}</div>
<div style={{ fontSize: '7pt', color: COLORS.slate400, fontVariantNumeric: 'tabular-nums' }}>{String(i + 1).padStart(2, '0')}</div>
</div>
<div style={{ fontSize: '8pt', color: COLORS.slate600, lineHeight: 1.4, marginBottom: '2mm' }}>{m.desc}</div>
<div style={{ marginTop: 'auto', borderTop: `1px solid ${COLORS.slate100}`, paddingTop: '2mm', fontSize: '7pt', color: COLORS.slate500 }}>{m.features.join(' · ')}</div>
</div>
)
})}
</div>
<div style={{ marginTop: '4mm', flexShrink: 0 }}>
<Callout tone="positive" label={de ? 'Pricing-Logik' : 'Pricing logic'}>
{de
? 'Starter <10 MA: 3.600 €/J · Professional 10250: 1540k €/J · Enterprise 250+: ab 50k €/J. Mitarbeiterbasiert. Standard: BSI-Cloud DE. Optional: Mac Mini/Studio für absolute Privacy bei Kleinunternehmen.'
: 'Starter <10 emp: €3,600/yr · Professional 10250: €1540k/yr · Enterprise 250+: from €50k/yr. Employee-based. Standard: BSI cloud DE. Optional: Mac Mini/Studio for absolute privacy for micro businesses.'}
</Callout>
</div>
</Page>
)
}
/* ===== HOW IT WORKS ===== */
const STEPS_DE = [
{ n: '01', t: 'Cloud-Vertrag abschließen', d: 'BSI-zertifizierte Cloud in Deutschland. Fixe oder flexible Kosten je nach Volumen. Onboarding-Call mit dedicated CSM in der ersten Woche.' },
{ n: '02', t: 'Code-Repos verbinden', d: 'Git-Repos, CI/CD-Pipelines und Firmware-Projekte über Standard-Integrationen anbinden. Die KI scannt automatisch, bei jeder Änderung.' },
{ n: '03', t: 'Compliance & Security automatisieren', d: 'Kontinuierliche Code-Analyse, Pentesting und Risikoanalysen. VVT, TOMs, DSFA, CE-Dokumentation werden automatisch erstellt und aktualisiert.' },
{ n: '04', t: 'Audit vorbereiten', d: 'Alle Nachweise, Dokumente und Risikobeurteilungen auf Knopfdruck. Abweichungen nach dem Audit automatisch nachverfolgt, Stichtage, Tickets, Eskalation.' },
]
const STEPS_EN = [
{ n: '01', t: 'Sign cloud contract', d: 'BSI-certified cloud in Germany. Fixed or flexible costs depending on volume. Onboarding call with dedicated CSM in week one.' },
{ n: '02', t: 'Connect code repos', d: 'Connect Git repos, CI/CD pipelines and firmware projects via standard integrations. The AI scans automatically, on every change.' },
{ n: '03', t: 'Automate compliance & security', d: 'Continuous code analysis, pentesting and risk assessments. RoPA, TOMs, DPIA, CE documentation auto-generated and updated.' },
{ n: '04', t: 'Prepare for audit', d: 'All evidence, documents and risk assessments at the push of a button. Post-audit deviations automatically tracked, deadlines, tickets, escalation.' },
]
const TIMELINE_DAYS_DE = ['Tag 0', 'Tag 3', 'Tag 7', 'Tag 14', 'Tag 30']
const TIMELINE_DAYS_EN = ['Day 0', 'Day 3', 'Day 7', 'Day 14', 'Day 30']
const TIMELINE_LABELS_DE = ['Vertrag', 'Onboarding-Call', 'Repos angebunden', 'VVT/TOMs auto', 'audit-ready']
const TIMELINE_LABELS_EN = ['Contract', 'Onboarding call', 'Repos connected', 'RoPA/TOMs auto', 'audit-ready']
export function PrintHowItWorksPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
const de = lang === 'de'
const steps = de ? STEPS_DE : STEPS_EN
const days = de ? TIMELINE_DAYS_DE : TIMELINE_DAYS_EN
const labels = de ? TIMELINE_LABELS_DE : TIMELINE_LABELS_EN
const MONO = "'JetBrains Mono', ui-monospace, monospace"
return (
<Page kicker="08" section={de ? 'SO FUNKTIONIERT\'S' : 'HOW IT WORKS'} title={de ? 'In 4 Schritten zur kontinuierlichen Compliance.' : 'Continuous compliance in 4 steps.'} subtitle={de ? 'Vom Vertrag bis zur Audit-Bereitschaft in der Regel <30 Tage. Kein Excel, kein Pentest-Vendor, keine manuelle Dokumentenpflege.' : 'From contract to audit-ready typically in <30 days. No Excel, no pentest vendor, no manual document maintenance.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
{/* 4-step rail: numbered violet circles on a horizontal connector line,
title + body underneath each. Replaces the floating-arrow StepStrip. */}
<div style={{ position: 'relative', marginTop: '4mm', flexShrink: 0 }}>
{/* connector line (behind the circles) */}
<div style={{ position: 'absolute', top: '7mm', left: '7mm', right: '7mm', height: '2px', background: `linear-gradient(90deg, ${COLORS.violet600} 0%, ${COLORS.violet400} 100%)`, opacity: 0.85, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
<div style={{ position: 'relative', display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '5mm' }}>
{steps.map((s, i) => (
<div key={i} style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}>
{/* number circle on the rail */}
<div style={{ width: '14mm', height: '14mm', borderRadius: '50%', background: COLORS.violet600, color: '#ffffff', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '14pt', fontWeight: 800, letterSpacing: '-0.01em', boxShadow: `0 0 0 4px ${COLORS.violet50}`, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>{s.n}</div>
<div style={{ marginTop: '3mm', fontSize: '12pt', fontWeight: 700, color: COLORS.slate900, lineHeight: 1.15, letterSpacing: '-0.005em' }}>{s.t}</div>
<div style={{ marginTop: '2mm', fontSize: '8.5pt', color: COLORS.slate700, lineHeight: 1.5 }}>{s.d}</div>
</div>
))}
</div>
</div>
{/* Fill space between steps and footer with a visual timeline */}
<div style={{ flex: 1 }} />
<div style={{ flexShrink: 0, marginBottom: '2mm' }}>
<div style={{ fontFamily: MONO, fontSize: '7.5pt', fontWeight: 700, color: COLORS.violet600, textTransform: 'uppercase', letterSpacing: '0.18em', marginBottom: '4mm' }}>
{de ? 'Time-to-Value · Median 14 Tage · Worst Case 28 Tage' : 'Time-to-Value · Median 14 days · Worst case 28 days'}
</div>
{/* dotted timeline with 5 day markers */}
<div style={{ position: 'relative', height: '14mm' }}>
{/* the rail */}
<div style={{ position: 'absolute', left: '7mm', right: '7mm', top: '5mm', height: '1.5px', background: `repeating-linear-gradient(90deg, ${COLORS.violet400} 0, ${COLORS.violet400} 2mm, transparent 2mm, transparent 4mm)`, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', height: '100%' }}>
{days.map((d, i) => (
<div key={i} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', position: 'relative' }}>
{/* day marker pill */}
<div style={{ fontFamily: MONO, fontSize: '7pt', fontWeight: 700, color: COLORS.violet700, background: COLORS.violet50, padding: '1mm 3mm', borderRadius: '99pt', border: `1px solid ${COLORS.violet300}`, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>{d}</div>
{/* dot on rail */}
<div style={{ width: '3mm', height: '3mm', borderRadius: '50%', background: COLORS.violet600, marginTop: '1mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
{/* label below */}
<div style={{ marginTop: '2mm', fontSize: '7.5pt', color: COLORS.slate700, textAlign: 'center', fontWeight: 600 }}>{labels[i]}</div>
</div>
))}
</div>
</div>
</div>
</Page>
)
}
/* ===== BUSINESS MODEL / PRICING ===== */
export function PrintBusinessModelPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
const de = lang === 'de'
const MONO = "'JetBrains Mono', ui-monospace, monospace"
const tiers = [
{
name: 'Starter',
target: de ? '< 25 Mitarbeiter · Basis-Module' : '< 25 employees · basic modules',
price: '€3.600',
unit: de ? '/ Jahr' : '/ year',
features: de
? ['DSGVO + Audit + DSR-Workflow', 'Compliance Scanner (CI/CD)', 'EU-Hosting · BSI C5', 'E-Mail-Support']
: ['GDPR + Audit + DSR workflow', 'Compliance Scanner (CI/CD)', 'EU hosting · BSI C5', 'Email support'],
tint: COLORS.violet400,
bg: COLORS.violet50,
featured: false,
},
{
name: 'Professional',
target: de ? '25250 Mitarbeiter · alle Module' : '25250 employees · all modules',
price: '€18.000',
unit: de ? '/ Jahr' : '/ year',
features: de
? ['Alle 12 Module', 'Priority-Support · Onboarding-Call', 'CE-Software-Risiko + Tender Matching', 'Dedicated CSM · 14-tägige Reviews', 'Custom-Integrationen (Jira, GitLab)']
: ['All 12 modules', 'Priority support · onboarding call', 'CE software risk + tender matching', 'Dedicated CSM · biweekly reviews', 'Custom integrations (Jira, GitLab)'],
tint: COLORS.violet600,
bg: `linear-gradient(180deg, ${COLORS.violet50} 0%, #ffffff 60%, ${COLORS.violet50} 100%)`,
featured: true,
},
{
name: 'Enterprise',
target: de ? '250+ Mitarbeiter · maßgeschneidert' : '250+ employees · custom',
price: de ? 'ab €50.000' : 'from €50k',
unit: de ? '/ Jahr' : '/ year',
features: de
? ['Alles aus Professional', 'SLA · Custom Contract', 'On-Premise / Air-Gap (Mac Mini/Studio)', 'Dedicated Customer Engineering', 'Multi-Region Audit-Trail']
: ['Everything in Professional', 'SLA · custom contract', 'On-premise / air-gap (Mac Mini/Studio)', 'Dedicated customer engineering', 'Multi-region audit trail'],
tint: COLORS.amber600,
bg: COLORS.amber50,
featured: false,
},
]
return (
<Page kicker="10" section={de ? 'PREISE' : 'PRICING'} title={de ? 'Drei Tiers. Mitarbeiterbasiert. ROI ab Tag 1.' : 'Three tiers. Employee-based. ROI from day 1.'} subtitle={de ? 'Recurring Revenue · BSI-Cloud DE Standard · Optional Hardware. Kunden zahlen ~50k/Jahr und sparen €112k.' : 'Recurring revenue · BSI cloud DE standard · optional hardware. Customers pay ~€50k/yr and save €112k.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
{/* 3 product cards */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '5mm', marginBottom: '5mm', position: 'relative' }}>
{tiers.map((t) => (
<div key={t.name} style={{
background: t.bg,
border: `${t.featured ? '2px' : '1px'} solid ${t.tint}`,
borderRadius: '6pt',
padding: '5mm',
display: 'flex',
flexDirection: 'column',
position: 'relative',
boxShadow: t.featured ? `0 6px 18px ${COLORS.violet600}25` : 'none',
WebkitPrintColorAdjust: 'exact',
printColorAdjust: 'exact',
}}>
{/* Featured badge */}
{t.featured && (
<div style={{ position: 'absolute', top: '-3.5mm', left: '50%', transform: 'translateX(-50%)', fontFamily: MONO, fontSize: '7pt', fontWeight: 700, color: '#ffffff', background: COLORS.violet600, padding: '1mm 4mm', borderRadius: '99pt', textTransform: 'uppercase', letterSpacing: '0.18em', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>{de ? 'Beliebt' : 'Popular'}</div>
)}
<div style={{ fontFamily: MONO, fontSize: '8pt', fontWeight: 700, color: t.tint, textTransform: 'uppercase', letterSpacing: '0.22em', marginBottom: '2mm' }}>{t.name}</div>
<div style={{ fontSize: '8.5pt', color: COLORS.slate600, marginBottom: '4mm', lineHeight: 1.4 }}>{t.target}</div>
{/* Price */}
<div style={{ display: 'flex', alignItems: 'baseline', gap: '2mm', marginBottom: '4mm' }}>
<div style={{ fontSize: '28pt', fontWeight: 800, color: COLORS.slate900, lineHeight: 1, fontVariantNumeric: 'tabular-nums', letterSpacing: '-0.025em' }}>{t.price}</div>
<div style={{ fontSize: '9pt', color: COLORS.slate500, fontWeight: 500 }}>{t.unit}</div>
</div>
{/* Features */}
<div style={{ display: 'flex', flexDirection: 'column', gap: '1.5mm' }}>
{t.features.map((f, i) => (
<div key={i} style={{ display: 'flex', alignItems: 'flex-start', gap: '2mm' }}>
<span style={{ color: COLORS.emerald600, fontSize: '9pt', fontWeight: 700, marginTop: '0.5pt' }}></span>
<span style={{ fontSize: '8.5pt', color: COLORS.slate700, lineHeight: 1.4 }}>{f}</span>
</div>
))}
</div>
</div>
))}
</div>
{/* Unit economics + savings — bottom strip */}
<div style={{ display: 'grid', gridTemplateColumns: '1.4fr 1fr', gap: '6mm', flex: 1, minHeight: 0 }}>
<div>
<div style={{ fontFamily: MONO, fontSize: '7.5pt', fontWeight: 700, color: COLORS.violet600, textTransform: 'uppercase', letterSpacing: '0.18em', marginBottom: '2mm' }}>{de ? 'Unit Economics · Reifephase' : 'Unit Economics · Mature'}</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '3mm' }}>
{[
{ n: '~70%', l: de ? 'Bruttomarge' : 'Gross margin', tone: 'positive' as const },
{ n: '~3,5×', l: 'LTV / CAC', tone: 'positive' as const },
{ n: '~14m', l: de ? 'CAC-Payback' : 'CAC payback' },
{ n: '<8%', l: de ? 'Net Churn p.a.' : 'Net churn p.a.', tone: 'positive' as const },
].map((k, i) => (
<div key={i} style={{ border: `1px solid ${COLORS.slate200}`, padding: '3mm', background: '#ffffff', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<div style={{ fontSize: '18pt', fontWeight: 800, color: k.tone === 'positive' ? COLORS.emerald700 : COLORS.slate900, lineHeight: 1, fontVariantNumeric: 'tabular-nums', letterSpacing: '-0.015em' }}>{k.n}</div>
<div style={{ fontFamily: MONO, fontSize: '6.5pt', color: COLORS.slate500, marginTop: '2mm', textTransform: 'uppercase', letterSpacing: '0.12em', fontWeight: 700 }}>{k.l}</div>
</div>
))}
</div>
</div>
<div style={{ background: COLORS.emerald50, border: `1px solid ${COLORS.emerald600}`, borderRadius: '4pt', padding: '4mm 5mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<div style={{ fontFamily: MONO, fontSize: '7pt', color: COLORS.emerald700, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.18em', marginBottom: '2mm' }}>{de ? 'Netto-Effekt · KMU 50 MA / Jahr 1' : 'Net effect · SME 50 emp. / Y1'}</div>
<div style={{ display: 'flex', alignItems: 'baseline', gap: '3mm' }}>
<div style={{ fontSize: '26pt', fontWeight: 800, color: COLORS.emerald700, lineHeight: 1, fontVariantNumeric: 'tabular-nums', letterSpacing: '-0.02em' }}>+30k</div>
<div style={{ fontSize: '9pt', color: COLORS.emerald700, fontWeight: 600 }}>{de ? 'pro KMU / Jahr' : 'per SME / yr'}</div>
</div>
<div style={{ fontSize: '8pt', color: COLORS.slate700, marginTop: '2mm', lineHeight: 1.4 }}>
{de ? 'Kunde spart €55k (Pentests, CE-Risiko, Compliance-Zeit), zahlt €25k. ROI ab Tag 1.' : 'Customer saves €55k (pentests, CE risk, compliance time), pays €25k. ROI from day 1.'}
</div>
</div>
</div>
</Page>
)
}
+30 -18
View File
@@ -6,6 +6,7 @@ import {
PitchCompetitor, PitchFeature, PitchMilestone, PitchMetric, PitchFunding, PitchProduct,
FpScenarioRef, FMResult, FMAssumption,
} from '@/lib/types'
import { finanzplanToFMResults } from '@/lib/finanzplan/adapter'
import PrintDeck from './_components/PrintDeck'
interface Ctx {
@@ -65,28 +66,39 @@ 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.
//
// Data source: the live `fp_*` tables (same as the interactive deck), bridged
// to FMResult[] via finanzplanToFMResults. The legacy `pitch_fm_results` table
// is no longer populated by the current pipeline.
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<Record<string, unknown>>
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
// Snapshot stores fp_scenario IDs under `fm_scenarios`; fall back to the live
// default fp scenario if the snapshot is empty (older versions).
let scenarioId: string | null = defaultScenario?.id ? String(defaultScenario.id) : null
if (!scenarioId) {
const liveRes = await pool.query(`SELECT id FROM fp_scenarios WHERE is_default = true LIMIT 1`)
scenarioId = liveRes.rows[0]?.id ? String(liveRes.rows[0].id) : null
}
if (scenarioId) {
try {
const fpResponse = await finanzplanToFMResults(pool, scenarioId)
fmResults = fpResponse.results
} catch {
fmResults = []
}
}
const rawAssumptions = (map.fm_assumptions || []) as Array<Record<string, unknown>>
fmAssumptions = rawAssumptions.map(a => ({
...a,
value: typeof a.value === 'string' ? JSON.parse(a.value as string) : a.value,
})) as FMAssumption[]
return (
<PrintDeck
+47 -11
View File
@@ -1,9 +1,30 @@
/* Fonts: Inter (body/headings) + JetBrains Mono (kickers, tickers, page numbers).
Plus Jakarta Sans is still loaded by globals.css; we don't need it for print. */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600;700&display=swap');
/* Named page — must be outside @media print */
@page slide-page {
size: A4 landscape;
margin: 0;
}
/*
* Page background: violet-tinted radial gradient with a faint dotted-grid
* overlay (printed via two layered background-images on .print-page).
*
* The radial mimics Claude Design's `radial-gradient(ellipse at 50% 12%, #fff 0%, #f5efff 55%, #ebdfff 100%)`.
* The dots are a tiny SVG repeat tile at 24px pitch, ~6% slate, so the grid is
* just barely visible same role as the dot-grid in the design reference.
*/
.print-page-bg {
background-color: #ffffff;
background-image:
url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Ccircle cx='1' cy='1' r='0.6' fill='%23a78bfa' fill-opacity='0.18'/%3E%3C/svg%3E"),
radial-gradient(ellipse at 50% 8%, #ffffff 0%, #f5efff 55%, #ebdfff 100%);
background-repeat: repeat, no-repeat;
background-size: 24px 24px, cover;
}
@media screen {
body { background: #d1d5db; }
}
@@ -14,22 +35,19 @@
margin: 0;
}
/*
* globals.css sets html,body { height:100%; overflow:hidden; background:#0a0a1a }.
* In print mode that clips all content to one viewport height and renders a black
* background. Override everything here.
*/
html, body {
height: auto !important;
min-height: 0 !important;
overflow: visible !important;
background: #ffffff !important;
color: #000000 !important;
color: #1a0f34 !important;
margin: 0 !important;
padding: 0 !important;
font-family: 'Inter', 'Plus Jakarta Sans', system-ui, -apple-system, sans-serif !important;
-webkit-print-color-adjust: exact;
-moz-print-color-adjust: exact;
print-color-adjust: exact;
font-variant-numeric: tabular-nums;
}
.no-print {
@@ -41,12 +59,10 @@
margin: 0 !important;
display: block !important;
overflow: visible !important;
font-family: 'Inter', system-ui, sans-serif !important;
}
/*
* Block wrapper: carries the height AND the page break.
* height:210mm on display:block is reliable in both Chrome and Firefox.
*/
/* Block wrapper carries the height + page break. */
.print-page-break {
page: slide-page;
display: block !important;
@@ -72,9 +88,29 @@
overflow: hidden !important;
margin: 0 !important;
box-shadow: none !important;
background: #ffffff !important;
font-family: 'Inter', system-ui, sans-serif !important;
color: #1a0f34 !important;
-webkit-print-color-adjust: exact;
-moz-print-color-adjust: exact;
print-color-adjust: exact;
}
/* Tabular numerals everywhere */
.print-page table, .print-page .num, .print-page .kpi {
font-variant-numeric: tabular-nums;
}
/* Mono utility — for kickers, page numbers, tickers, code-style tags */
.print-mono {
font-family: 'JetBrains Mono', ui-monospace, Menlo, Consolas, monospace !important;
}
}
/* Screen preview: same fonts, applied to print-page on screen too */
.print-page, .print-page-break {
font-family: 'Inter', system-ui, sans-serif;
font-variant-numeric: tabular-nums;
}
.print-mono {
font-family: 'JetBrains Mono', ui-monospace, Menlo, Consolas, monospace;
}
+6 -3
View File
@@ -17,18 +17,20 @@ export async function finanzplanToFMResults(pool: Pool, scenarioId?: string): Pr
}
// Load computed data
const [personalRes, liquidRes, betriebRes, umsatzRes, materialRes, investRes] = await Promise.all([
const [personalRes, liquidRes, betriebRes, umsatzRes, materialRes, investRes, kundenRes] = await Promise.all([
pool.query("SELECT * FROM fp_personalkosten WHERE scenario_id = $1 ORDER BY sort_order", [sid]),
pool.query("SELECT * FROM fp_liquiditaet WHERE scenario_id = $1 ORDER BY sort_order", [sid]),
pool.query("SELECT * FROM fp_betriebliche_aufwendungen WHERE scenario_id = $1 ORDER BY sort_order", [sid]),
pool.query("SELECT * FROM fp_umsatzerloese WHERE scenario_id = $1 AND section = 'revenue' AND row_label = 'GESAMTUMSATZ' LIMIT 1", [sid]),
pool.query("SELECT * FROM fp_materialaufwand WHERE scenario_id = $1 AND row_label = 'SUMME' LIMIT 1", [sid]),
pool.query("SELECT * FROM fp_investitionen WHERE scenario_id = $1 ORDER BY sort_order", [sid]),
pool.query("SELECT * FROM fp_kunden_summary WHERE scenario_id = $1 AND row_label = 'Bestandskunden gesamt' LIMIT 1", [sid]),
])
const personal = personalRes.rows
const liquid = liquidRes.rows
const betrieb = betriebRes.rows
const customersByMonth = (kundenRes.rows[0]?.values as MonthlyValues) || emptyMonthly()
// Helper to sum a field across personnel
function sumPersonalField(field: string): MonthlyValues {
@@ -92,9 +94,9 @@ export async function finanzplanToFMResults(pool: Pool, scenarioId?: string): Pr
month: m,
year,
month_in_year: month,
new_customers: 0,
new_customers: Math.max((customersByMonth[`m${m}`] || 0) - prevCustomers, 0),
churned_customers: 0,
total_customers: 0,
total_customers: Math.round(customersByMonth[`m${m}`] || 0),
mrr_eur: Math.round(rev / 1), // monthly
arr_eur: Math.round(rev * 12),
revenue_eur: Math.round(rev),
@@ -113,6 +115,7 @@ export async function finanzplanToFMResults(pool: Pool, scenarioId?: string): Pr
cash_balance_eur: Math.round(cash),
cumulative_revenue_eur: Math.round(cumulativeRevenue),
})
prevCustomers = customersByMonth[`m${m}`] || 0
}
// Summary