feat(pitch-print): redesign PDF investor brief from scratch
Build pitch-deck / build-push-deploy (push) Successful in 2m19s
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 46s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 34s
Build pitch-deck / build-push-deploy (push) Successful in 2m19s
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 46s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 34s
Throws away the screen-deck-derived print system. Builds a new institutional-research aesthetic: - 12-col grid on A4 landscape, hairline rules, no colored bars, no icons - 3-color discipline: indigo (structural), emerald (positive), red (problem) - Plus Jakarta Sans 800 for hero numerals + titles; tabular numerals everywhere - 1-to-1 content parity with the interactive deck: full USP (8 cards), full competition matrix (45 features, 12 AppSec features, 8+6 competitor profiles), Finanzplan P&L grid + KPI dashboard, full glossary - 2-page slides where content demands (Exec Summary, USP, Competition, Finanzplan) - 28 base pages; +1 for Financial detail; +1 for Cap Table (suppressed on Wandeldarlehen) Files: - New: PrintIntroSlides, PrintProductSlides, PrintMarketSlides, PrintCompetitionSlides - Rewritten: PrintLayout (new primitives Page/KpiRow/TwoCol/ThreeCol/DataTable/MatrixGlyph/Callout), PrintAnnexSlides, PrintFinancialSlides, PrintDeck - Removed: PrintCoreSlides.tsx, PrintExtraSlides.tsx (obsolete) - print.css now sets Plus Jakarta Sans as the print font family - All files under 500 LOC cap Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,478 +1,500 @@
|
||||
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'
|
||||
|
||||
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: '75–150k 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,5–1,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: '2–4M 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: '4–10M 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: '75–150k 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.5–1.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: '2–4M 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: '4–10M EUR', items: ['EU expansion (AT, CH, Benelux)', 'Enterprise sales', 'Developer Relations (Snyk model)', 'Break-even or Series A'] },
|
||||
]
|
||||
/* ===== 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 10–250 MA',
|
||||
'White-Glove-Onboarding: persönlich, hands-on',
|
||||
'2 Referenzkunden aus Region Konstanz/Bodensee',
|
||||
'Case Studies, Testimonials, Referenz-Calls',
|
||||
'Ziel: 2 zahlende Kunden, ARR €30–50k',
|
||||
] : [
|
||||
'GmbH incorporation Breakpilot COMPLAI',
|
||||
'Direct sales to manufacturers 10–250 emp.',
|
||||
'White-glove onboarding: personal, hands-on',
|
||||
'2 reference customers from Konstanz/Bodensee region',
|
||||
'Case studies, testimonials, reference calls',
|
||||
'Goal: 2 paying customers, ARR €30–50k',
|
||||
],
|
||||
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 (5–10)',
|
||||
'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: 5–7 (Eng + 2 Sales + 1 CSM)',
|
||||
'Ziel: 50–80 Kunden, ARR €1,2–2M',
|
||||
] : [
|
||||
'Channel partnerships with IT integrators (5–10)',
|
||||
'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: 5–7 (eng + 2 sales + 1 CSM)',
|
||||
'Goal: 50–80 customers, ARR €1.2–2M',
|
||||
],
|
||||
kpi: de ? '50–80 Kunden · ARR €1,5M' : '50–80 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 (50–500 MA): dedicated AE-Team',
|
||||
'EU-Expansion: AT (DACH-nativ), CH, Benelux',
|
||||
'Distributor-Partnerschaften für Frankreich, Italien',
|
||||
'Branchenausweitung: Automotive, Pharma, Energie',
|
||||
'Series A vorbereiten (€8–12M, Q2 2029)',
|
||||
'Break-Even Q3 / 2029',
|
||||
'Ziel: 200+ Kunden, ARR €8–12M',
|
||||
] : [
|
||||
'Enterprise customers (50–500 emp.): dedicated AE team',
|
||||
'EU expansion: AT (DACH-native), CH, Benelux',
|
||||
'Distributor partnerships for France, Italy',
|
||||
'Industry expansion: Automotive, Pharma, Energy',
|
||||
'Prepare Series A (€8–12M, Q2 2029)',
|
||||
'Break-even Q3 / 2029',
|
||||
'Goal: 200+ customers, ARR €8–12M',
|
||||
],
|
||||
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>
|
||||
<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) => (
|
||||
<div key={i} style={{ borderLeft: `2px solid ${p.tone}`, paddingLeft: '5mm', display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: '2mm' }}>
|
||||
<span style={{ fontSize: '28pt', fontWeight: 800, color: p.tone, lineHeight: 1, fontVariantNumeric: 'tabular-nums', letterSpacing: '-0.02em' }}>{p.n}</span>
|
||||
<span style={{ fontSize: '7pt', color: COLORS.slate500, textAlign: 'right', textTransform: 'uppercase', letterSpacing: '0.08em', fontWeight: 600 }}>{p.kpi}</span>
|
||||
</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>
|
||||
</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 style={{ fontSize: '12pt', fontWeight: 700, color: COLORS.slate900, marginTop: '2mm', 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 }}>
|
||||
<Bullets dense items={p.items} />
|
||||
</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 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>
|
||||
<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>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
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 ? '2026–2030 · Base Case · monatlich modelliert, jährlich aggregiert' : '2026–2030 · 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>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
/* ===== ANNEX REGULATORY ===== */
|
||||
|
||||
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'] },
|
||||
],
|
||||
}
|
||||
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 4–6 verbindliche Regelwerke ab. BreakPilot mappt diese auf 25.000+ atomare Controls.' : 'Each pillar covers 4–6 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. Eine LiteLLM-Gateway. Lokale Inferenz.' : 'Three product domains. One LiteLLM gateway. Local inference.'} subtitle={de ? 'BreakPilot · CERTifAI · Compliance Scanner, über LiteLLM-Proxy mit lokalen LLM-Inferenz-Knoten verbunden. 100% EU, kein US-Anbieter.' : 'BreakPilot · CERTifAI · Compliance Scanner, connected via LiteLLM proxy to local LLM inference nodes. 100% EU, no US providers.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
|
||||
|
||||
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>
|
||||
<div style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }}>
|
||||
{/* PRODUCT TIER */}
|
||||
<div style={{ marginBottom: '4mm' }}>
|
||||
<div style={{ fontSize: '7.5pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.12em', marginBottom: '2mm' }}>{de ? 'Produkt-Schicht' : 'Product Tier'}</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '4mm' }}>
|
||||
{[
|
||||
{ t: 'CERTifAI', s: de ? 'GenAI Mandantenportal' : 'GenAI Tenant Portal', tech: 'Rust · Dioxus · MongoDB · Keycloak · SearXNG · LangGraph', services: ['LiteLLM Dashboard', 'LibreChat + SSO', 'LangGraph Agents', 'MCP Hub'] },
|
||||
{ t: 'COMPLAI', s: de ? 'Compliance & Audit' : 'Compliance & Audit', tech: 'Next.js 15 · FastAPI · Go/Gin · PostgreSQL · Qdrant · Valkey', services: [de ? 'DSGVO/AI Act/NIS2 (25k+ Controls)' : 'GDPR/AI Act/NIS2 (25k+ controls)', de ? 'RAG (380+ Quellen)' : 'RAG (380+ sources)', de ? 'Control Pipeline (LLM)' : 'Control pipeline (LLM)', 'MCP Client'] },
|
||||
{ t: 'Compliance Scanner', s: de ? 'Code-Sicherheit' : 'Code Security', tech: 'Rust · Axum · MongoDB · Semgrep · Gitleaks · Syft', services: ['SAST · SBOM · CVE Pipeline', de ? 'KI-Triage (False Positives)' : 'AI Triage (false positives)', de ? 'KI-Pentest (autonom)' : 'AI Pentest (autonomous)', 'MCP Server'] },
|
||||
].map((p, i) => (
|
||||
<div key={i} style={{ border: `1px solid ${COLORS.slate200}`, borderTop: `2px solid ${COLORS.indigo600}`, padding: '3mm 4mm' }}>
|
||||
<div style={{ fontSize: '11pt', fontWeight: 700, color: COLORS.slate900, marginBottom: '0.5mm' }}>{p.t}</div>
|
||||
<div style={{ fontSize: '8pt', color: COLORS.indigo600, marginBottom: '2mm', fontWeight: 600 }}>{p.s}</div>
|
||||
<div style={{ fontSize: '7pt', color: COLORS.slate600, fontFamily: 'monospace', marginBottom: '2mm', lineHeight: 1.4 }}>{p.tech}</div>
|
||||
<div>
|
||||
{p.services.map((s, j) => (
|
||||
<div key={j} style={{ fontSize: '7.5pt', color: COLORS.slate700, padding: '0.8mm 0', borderTop: j > 0 ? `1px solid ${COLORS.slate100}` : 'none' }}>· {s}</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1.4fr', gap: '12px', flex: 1 }}>
|
||||
<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>
|
||||
|
||||
{/* PROXY TIER */}
|
||||
<div style={{ marginBottom: '4mm' }}>
|
||||
<div style={{ fontSize: '7.5pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.12em', marginBottom: '2mm' }}>{de ? 'Gateway-Schicht (KI-Proxy mit Guardrails)' : 'Gateway Tier (AI proxy with guardrails)'}</div>
|
||||
<div style={{ border: `1px solid ${COLORS.amber600}`, borderTop: `2px solid ${COLORS.amber600}`, padding: '3mm 4mm', background: COLORS.amber50, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'baseline', gap: '4mm', marginBottom: '2mm' }}>
|
||||
<span style={{ fontSize: '11pt', fontWeight: 700, color: COLORS.slate900 }}>LiteLLM Proxy</span>
|
||||
<span style={{ fontSize: '8pt', color: COLORS.amber700, fontWeight: 600 }}>{de ? 'KI-Gateway · Bearer-Auth · Rate-Limiting · PII-Filter · Spend-Tracking' : 'AI gateway · bearer auth · rate limiting · PII filter · spend tracking'}</span>
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: '3mm', fontSize: '7.5pt', color: COLORS.slate700 }}>
|
||||
<div>· {de ? 'Token-Budget pro Mandant' : 'Per-tenant token budget'}</div>
|
||||
<div>· {de ? 'PII-Guardrails alle Anfragen' : 'PII guardrails on all requests'}</div>
|
||||
<div>· {de ? 'Anonyme EU-Web-Suche (SearXNG)' : 'Anonymous EU web search (SearXNG)'}</div>
|
||||
<div>· {de ? 'Namespace-Isolierung pro API-Key' : 'Namespace isolation per API key'}</div>
|
||||
<div>· {de ? 'Failover-Routing zwischen Modellen' : 'Failover routing between models'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* INFERENCE TIER */}
|
||||
<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 style={{ fontSize: '7.5pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.12em', marginBottom: '2mm' }}>{de ? 'Inferenz-Schicht (lokal, air-gap-fähig)' : 'Inference Tier (local, air-gap capable)'}</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '4mm' }}>
|
||||
{[
|
||||
{ t: de ? 'LLM Inferenz' : 'LLM Inference', tech: 'Qwen3-32B · Qwen3-Coder-30B · DeepSeek-R1-8B · Ollama', desc: de ? 'Vollständig lokal, air-gap fähig, GPU-optimiert. Daten verlassen nie den Server.' : 'Fully local, air-gap capable, GPU-optimized. Data never leaves the server.' },
|
||||
{ t: 'Embeddings', tech: 'bge-m3 · Qdrant Vector DB · Sentence-Transformers', desc: de ? 'RAG mit 380+ Rechtsquellen indexiert, multilinguale Einbettungen, lokal.' : 'RAG with 380+ legal sources indexed, multilingual embeddings, local.' },
|
||||
{ t: de ? 'KI-Tools' : 'AI Tools', tech: 'SearXNG · MCP Protocol · Semgrep API · Gitleaks API', desc: de ? 'Anonymisierte EU-Web-Suche, MCP-Integration für Audit-Dokumente und Code-Findings.' : 'Anonymized EU web search, MCP integration for audit docs and code findings.' },
|
||||
].map((p, i) => (
|
||||
<div key={i} style={{ border: `1px solid ${COLORS.slate200}`, padding: '3mm 4mm' }}>
|
||||
<div style={{ fontSize: '10pt', fontWeight: 700, color: COLORS.slate900, marginBottom: '1mm' }}>{p.t}</div>
|
||||
<div style={{ fontSize: '7pt', color: COLORS.slate500, fontFamily: 'monospace', marginBottom: '2mm', lineHeight: 1.4 }}>{p.tech}</div>
|
||||
<div style={{ fontSize: '8pt', color: COLORS.slate700, lineHeight: 1.45 }}>{p.desc}</div>
|
||||
</div>
|
||||
))}
|
||||
</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 ENGINEERING ===== */
|
||||
|
||||
export function PrintEngineeringPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
|
||||
const de = lang === 'de'
|
||||
return (
|
||||
<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={{ 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: '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>
|
||||
<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>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
/* ===== 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 E0–E3', '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 E0–E3', '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>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1.3fr 1fr', gap: '10px', flex: 1 }}>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<div style={{ fontSize: '7.5pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.12em', marginBottom: '2mm' }}>{de ? 'Pipeline-Fluss' : 'Pipeline flow'}</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '4mm' }}>
|
||||
{[
|
||||
{ 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' },
|
||||
].map((s, i) => (
|
||||
<div key={i} style={{ borderLeft: `2px solid ${COLORS.indigo600}`, paddingLeft: '4mm', display: 'flex', flexDirection: 'column' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: '2mm' }}>
|
||||
<span style={{ fontSize: '14pt', fontWeight: 800, color: COLORS.indigo600, lineHeight: 1, fontVariantNumeric: 'tabular-nums' }}>{s.n}</span>
|
||||
<span style={{ fontSize: '7.5pt', color: COLORS.emerald700, fontWeight: 700, fontVariantNumeric: 'tabular-nums' }}>{s.kpi}</span>
|
||||
</div>
|
||||
<div style={{ fontSize: '11pt', fontWeight: 700, color: COLORS.slate900, marginBottom: '2mm', lineHeight: 1.2 }}>{s.t}</div>
|
||||
<div style={{ fontSize: '8pt', color: COLORS.slate700, lineHeight: 1.5 }}>{s.d}</div>
|
||||
</div>
|
||||
))}
|
||||
</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>
|
||||
|
||||
{/* 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={{ 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>
|
||||
</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,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,31 @@
|
||||
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 {
|
||||
PrintFinanzplanPage1, PrintFinanzplanPage2, PrintAssumptionsPage,
|
||||
PrintFinancialsPage, PrintCapTablePage, PrintDisclaimerPage,
|
||||
aggregateAnnualRows,
|
||||
} from './PrintFinancialSlides'
|
||||
|
||||
export { aggregateAnnualRows }
|
||||
|
||||
interface PrintDeckProps {
|
||||
pitchData: PitchData
|
||||
@@ -31,46 +39,50 @@ 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 hasFinancialDetail = financial && annualRows.length > 0
|
||||
const de = lang === 'de'
|
||||
|
||||
// Base standard PDF: 28 pages (25 slides, 3 of them 2-page: exec-summary, usp, competition; finanzplan annex is 2 pages)
|
||||
// 1 (exec1) + 1 (exec2) + 1 (cover) + 1 (problem) + 1 (solution) + 2 (usp) + 1 (regL) + 1 (product) +
|
||||
// 1 (how) + 1 (market) + 1 (bm) + 1 (traction) + 2 (competition) + 1 (team) + 1 (ask) + 1 (savings) +
|
||||
// 1 (strategy) + 2 (finanzplan) + 1 (assumptions) + 1 (regulatory) + 1 (architecture) + 1 (engineering) +
|
||||
// 1 (aipipeline) + 1 (risks) + 1 (glossary) + 1 (disclaimer) = 28
|
||||
const BASE_PAGES = 28
|
||||
const totalPages = BASE_PAGES + (hasFinancialDetail ? 1 : 0) + (hasCapTable ? 1 : 0)
|
||||
|
||||
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,61 +91,92 @@ 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: "'Plus Jakarta Sans', system-ui, sans-serif" }}>
|
||||
{/* SLIDE_ORDER from lib/slide-order.ts, minus 3 interactive-only slides:
|
||||
intro-presenter, ai-qa, annex-sdk-demo */}
|
||||
|
||||
{/* 1. executive-summary */}
|
||||
<PrintExecutiveSummaryPage market={pitchData.market || []} funding={pitchData.funding} {...p()} />
|
||||
{/* 2. cover (page 2 — uses its own layout; assign sequential number) */}
|
||||
{/* 1. executive-summary (2 pages) */}
|
||||
<PrintExecSummaryPage1 market={pitchData.market || []} {...p()} />
|
||||
<PrintExecSummaryPage2 {...p()} />
|
||||
|
||||
{/* 2. cover */}
|
||||
{(() => { n += 1; return <PrintCoverPage company={pitchData.company} funding={pitchData.funding} versionName={versionName} lang={lang} /> })()}
|
||||
|
||||
{/* 3. problem */}
|
||||
<PrintProblemPage {...p()} />
|
||||
|
||||
{/* 4. solution */}
|
||||
<PrintSolutionPage {...p()} />
|
||||
{/* 5. usp */}
|
||||
<PrintUSPPage {...p()} />
|
||||
|
||||
{/* 5. usp (2 pages) */}
|
||||
<PrintUSPPage1 {...p()} />
|
||||
<PrintUSPPage2 {...p()} />
|
||||
|
||||
{/* 6. regulatory-landscape */}
|
||||
<PrintRegulatoryLandscapePage {...p()} />
|
||||
{/* 7. product */}
|
||||
|
||||
{/* 7. product / modular-toolkit */}
|
||||
<PrintProductPage products={pitchData.products || []} {...p()} />
|
||||
|
||||
{/* 8. how-it-works */}
|
||||
<PrintHowItWorksPage {...p()} />
|
||||
|
||||
{/* 9. market */}
|
||||
<PrintMarketPage market={pitchData.market || []} {...p()} />
|
||||
{/* 10. business-model */}
|
||||
|
||||
{/* 10. business-model / pricing */}
|
||||
<PrintBusinessModelPage {...p()} />
|
||||
{/* 11. traction (uses milestones table) */}
|
||||
|
||||
{/* 11. traction (uses milestones) */}
|
||||
<PrintMilestonesPage milestones={pitchData.milestones || []} {...p()} />
|
||||
{/* 12. competition */}
|
||||
<PrintCompetitionPage {...p()} />
|
||||
|
||||
{/* 12. competition (2 pages) */}
|
||||
<PrintCompetitionPage1 {...p()} />
|
||||
<PrintCompetitionPage2 {...p()} />
|
||||
|
||||
{/* 13. team */}
|
||||
<PrintTeamPage team={pitchData.team || []} {...p()} />
|
||||
|
||||
{/* 14. the-ask */}
|
||||
<PrintTheAskPage funding={pitchData.funding} {...p()} />
|
||||
|
||||
{/* 15. customer-savings */}
|
||||
<PrintCustomerSavingsPage {...p()} />
|
||||
|
||||
{/* 16. annex-strategy */}
|
||||
<PrintStrategyPage {...p()} />
|
||||
{/* 17. annex-finanzplan */}
|
||||
<PrintFinanzplanPage fmResults={fmResults} {...p()} />
|
||||
{/* Financial-only: detailed P&L table */}
|
||||
{hasFinancials && <PrintFinancialsPage annualRows={annualRows} {...p()} />}
|
||||
|
||||
{/* 17. annex-finanzplan (2 pages) */}
|
||||
<PrintFinanzplanPage1 fmResults={fmResults} {...p()} />
|
||||
<PrintFinanzplanPage2 fmResults={fmResults} {...p()} />
|
||||
|
||||
{/* Financial-only: detail P&L */}
|
||||
{hasFinancialDetail && <PrintFinancialsPage annualRows={annualRows} {...p()} />}
|
||||
|
||||
{/* 18. annex-assumptions */}
|
||||
<PrintAssumptionsPage assumptions={fmAssumptions} {...p()} />
|
||||
|
||||
{/* 19. annex-regulatory */}
|
||||
<PrintRegulatoryPage {...p()} />
|
||||
|
||||
{/* 20. annex-architecture */}
|
||||
<PrintArchitecturePage {...p()} />
|
||||
|
||||
{/* 21. annex-engineering */}
|
||||
<PrintEngineeringPage {...p()} />
|
||||
|
||||
{/* 22. annex-aipipeline */}
|
||||
<PrintAIPipelinePage {...p()} />
|
||||
|
||||
{/* 23. risks */}
|
||||
<PrintRisksPage {...p()} />
|
||||
|
||||
{/* 24. annex-glossary */}
|
||||
<PrintGlossaryPage {...p()} />
|
||||
{/* Financial-only: cap table */}
|
||||
|
||||
{/* Financial-only: cap-table (suppressed for Wandeldarlehen) */}
|
||||
{hasCapTable && <PrintCapTablePage {...p()} />}
|
||||
|
||||
{/* 25. legal-disclaimer */}
|
||||
<PrintDisclaimerPage {...p()} />
|
||||
</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 20–200k 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 20–200k 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: '$10K–80K/yr', ai: 'full' as const },
|
||||
{ name: 'Drata', flag: 'US', founded: 2020, emp: '732', revenue: '$100M ARR', customers: '8.000', pricing: '$10K–100K/yr', ai: 'full' as const },
|
||||
{ name: 'Sprinto', flag: 'IN', founded: 2020, emp: '316', revenue: '$38M ARR', customers: '3.000', pricing: '$6K–25K/yr', ai: 'full' as const },
|
||||
{ name: 'DataGuard', flag: 'DE', founded: 2017, emp: '250', revenue: '~€52M', customers: '4.000', pricing: '€6K–24K+/yr', ai: 'partial' as const },
|
||||
{ name: 'Proliance', flag: 'DE', founded: 2017, emp: '65', revenue: '~€3.9M', customers: '2.000', pricing: '€1.5K–5.7K/yr', ai: 'none' as const },
|
||||
{ name: 'heyData', flag: 'DE', founded: 2020, emp: '58', revenue: '~€15M', customers: '2.000', pricing: '€1K–3.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,6 @@
|
||||
import { PrintPage, SectionTitle, PrintTable, Badge, COLORS } from './PrintLayout'
|
||||
import { Language, FMResult, FMAssumption } from '@/lib/types'
|
||||
import { Page, COLORS, Callout, DataTable } from './PrintLayout'
|
||||
import { computeAnnualKPIs } from '@/lib/finanzplan/annual-kpis'
|
||||
|
||||
interface SlideBase { lang: Language; pageNum: number; totalPages: number; versionName: string }
|
||||
|
||||
@@ -34,121 +35,231 @@ 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 2026–2030.' : 'Profit & Loss Statement 2026–2030.'} 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 maxRev = Math.max(...kpis.map(k => k.totalRevenue), 1)
|
||||
const maxEmp = Math.max(...kpis.map(k => k.employees), 1)
|
||||
|
||||
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. Quelle: aktuelles Base-Case-Szenario.' : '19 KPIs per year derived from fp_* tables. No hardcoded values. Source: current base-case scenario.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
|
||||
|
||||
{/* KPI table */}
|
||||
<div style={{ marginBottom: '4mm' }}>
|
||||
<DataTable
|
||||
dense
|
||||
cols={[
|
||||
{ header: de ? 'KPI' : 'KPI', width: '24%' },
|
||||
...kpis.map(k => ({ header: String(k.year), numeric: true, width: '15.2%' as string })),
|
||||
]}
|
||||
rows={[
|
||||
['ARR (Dez)', ...kpis.map(k => fmtEur(k.arr))],
|
||||
['MRR (Dez)', ...kpis.map(k => fmtEur(k.mrr))],
|
||||
[de ? 'ARPU (€/Monat)' : 'ARPU (€/mo)', ...kpis.map(k => fmtEur(k.arpu))],
|
||||
[de ? 'Kunden' : 'Customers', ...kpis.map(k => k.customers.toLocaleString('de-DE'))],
|
||||
[de ? 'Mitarbeiter' : 'Employees', ...kpis.map(k => k.employees.toLocaleString('de-DE'))],
|
||||
[de ? 'Umsatz / MA' : 'Revenue / FTE', ...kpis.map(k => fmtEur(k.revenuePerEmployee))],
|
||||
[de ? 'Bruttomarge %' : 'Gross margin %', ...kpis.map(k => k.grossMargin + '%')],
|
||||
[de ? 'EBIT' : 'EBIT', ...kpis.map(k => <span style={{ color: k.ebit >= 0 ? COLORS.emerald700 : COLORS.red700, fontWeight: 700 }}>{fmtEur(k.ebit)}</span>)],
|
||||
[de ? 'EBIT-Marge' : 'EBIT margin', ...kpis.map(k => <span style={{ color: k.ebitMargin >= 0 ? COLORS.emerald700 : COLORS.red700 }}>{k.ebitMargin}%</span>)],
|
||||
[de ? 'Steuern (~30%)' : 'Taxes (~30%)', ...kpis.map(k => fmtEur(k.taxes))],
|
||||
[de ? 'Netto-Ergebnis' : 'Net income', ...kpis.map(k => <strong style={{ color: k.netIncome >= 0 ? COLORS.emerald700 : COLORS.red700 }}>{fmtEur(k.netIncome)}</strong>)],
|
||||
[de ? 'Burn-Rate (Dez)' : 'Burn rate (Dec)', ...kpis.map(k => fmtEur(k.burnRate))],
|
||||
[de ? 'Runway (Monate)' : 'Runway (months)', ...kpis.map(k => k.runway == null ? '∞' : String(k.runway))],
|
||||
[de ? 'Cash-Bestand (Dez)' : 'Cash balance (Dec)', ...kpis.map(k => fmtEur(k.cashBalance))],
|
||||
]}
|
||||
highlightFirstCol
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Charts */}
|
||||
<div style={{ flex: 1, minHeight: 0, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6mm' }}>
|
||||
{/* Revenue chart */}
|
||||
<div>
|
||||
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '2mm' }}>{de ? 'Umsatz-Wachstum (Mio. €)' : 'Revenue growth (€M)'}</div>
|
||||
<div style={{ display: 'flex', alignItems: 'flex-end', height: '32mm', gap: '4mm', borderBottom: `1px solid ${COLORS.slate300}`, paddingBottom: '1mm' }}>
|
||||
{kpis.map((k, i) => {
|
||||
const h = (k.totalRevenue / maxRev) * 100
|
||||
return (
|
||||
<div key={i} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
||||
<div style={{ fontSize: '7pt', color: COLORS.indigo700, fontWeight: 700, marginBottom: '1mm', fontVariantNumeric: 'tabular-nums' }}>{(k.totalRevenue / 1e6).toFixed(1)}</div>
|
||||
<div style={{ width: '100%', height: `${h}%`, minHeight: '2pt', background: COLORS.indigo600, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '4mm', marginTop: '1mm' }}>
|
||||
{kpis.map((k, i) => (
|
||||
<div key={i} style={{ flex: 1, fontSize: '7pt', color: COLORS.slate500, textAlign: 'center', fontWeight: 600 }}>{k.year}</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Headcount + customers chart */}
|
||||
<div>
|
||||
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '2mm' }}>{de ? 'Mitarbeiter & Kunden' : 'Employees & customers'}</div>
|
||||
<div style={{ display: 'flex', alignItems: 'flex-end', height: '32mm', gap: '4mm', borderBottom: `1px solid ${COLORS.slate300}`, paddingBottom: '1mm' }}>
|
||||
{kpis.map((k, i) => {
|
||||
const h = (k.employees / maxEmp) * 100
|
||||
return (
|
||||
<div key={i} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', position: 'relative' }}>
|
||||
<div style={{ fontSize: '7pt', color: COLORS.slate700, marginBottom: '1mm', fontVariantNumeric: 'tabular-nums', textAlign: 'center', lineHeight: 1.2 }}>
|
||||
<strong style={{ color: COLORS.amber700 }}>{k.employees}</strong>
|
||||
<br />
|
||||
<span style={{ fontSize: '6.5pt', color: COLORS.slate500 }}>{k.customers}c</span>
|
||||
</div>
|
||||
<div style={{ width: '100%', height: `${h}%`, minHeight: '2pt', background: COLORS.amber600, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '4mm', marginTop: '1mm' }}>
|
||||
{kpis.map((k, i) => (
|
||||
<div key={i} style={{ flex: 1, fontSize: '7pt', color: COLORS.slate500, textAlign: 'center', fontWeight: 600 }}>{k.year}</div>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ marginTop: '2mm', fontSize: '7pt', color: COLORS.slate500, display: 'flex', gap: '6mm' }}>
|
||||
<span><span style={{ display: 'inline-block', width: '8pt', height: '8pt', background: COLORS.amber600 }} /> {de ? 'Mitarbeiter (FTE)' : 'Employees (FTE)'}</span>
|
||||
<span style={{ color: COLORS.slate500 }}>{de ? 'Zahl unten: Kunden' : 'Number below: customers'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</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)
|
||||
|
||||
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]) => (
|
||||
<Page kicker="18" section={de ? 'ANHANG · ANNAHMEN' : 'APPENDIX · ASSUMPTIONS'} title={de ? 'Skalare Annahmen, Base-Case-Szenario.' : 'Scalar assumptions, base-case scenario.'} subtitle={de ? 'Alle Treibervariablen aus den fp_assumptions-Tabellen. Drei Szenarien (Best/Base/Worst) für Sensitivitätsanalyse verfügbar.' : 'All driver variables from the fp_assumptions tables. Three scenarios (best/base/worst) available for sensitivity analysis.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8mm', flex: 1, minHeight: 0 }}>
|
||||
{categories.slice(0, 6).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' }}>
|
||||
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.indigo600, textTransform: 'uppercase', letterSpacing: '0.1em', borderBottom: `1px solid ${COLORS.indigo600}`, paddingBottom: '1mm', marginBottom: '2mm' }}>{cat}</div>
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '8pt', 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' }}>
|
||||
<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={{ color: COLORS.slate500, marginLeft: '1mm', fontWeight: 400, fontSize: '7pt' }}>{a.unit}</span>}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
@@ -157,121 +268,173 @@ export function PrintAssumptionsPage({ assumptions, lang, pageNum, totalPages, v
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</PrintPage>
|
||||
|
||||
<div style={{ marginTop: '4mm', flexShrink: 0 }}>
|
||||
<Callout tone="accent" label={de ? 'Sensitivität' : 'Sensitivity'}>
|
||||
{de
|
||||
? 'Drei Szenarien, Best/Base/Worst, variieren um Wachstumsrate, Churn, ARPU und CAC. Sensitivitäts-Tornado verfügbar im Live-Modell. Base-Case ist absichtlich konservativ angesetzt.'
|
||||
: 'Three scenarios, best/base/worst, vary growth rate, churn, ARPU and CAC. Sensitivity tornado available in live model. Base case is deliberately conservative.'}
|
||||
</Callout>
|
||||
</div>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
/* ===== FINANCIAL DETAIL (financial-only extra page) ===== */
|
||||
|
||||
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
|
||||
return (
|
||||
<Page kicker="17b" section={de ? 'ANHANG · DETAIL P&L' : 'APPENDIX · P&L DETAIL'} title={de ? 'Vollständige P&L mit Quartals-Aggregation.' : 'Full P&L with quarterly aggregation.'} subtitle={de ? 'Nur in der Financial-PDF-Version enthalten. Investor-only.' : 'Included only in Financial PDF version. Investor only.'} 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' }}>
|
||||
<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', height: '10mm', border: `1px solid ${COLORS.slate300}`, marginBottom: '6mm' }}>
|
||||
{CAP_TABLE_DATA.map(d => (
|
||||
<div key={d.name} style={{ width: `${d.pct}%`, backgroundColor: d.color, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
|
||||
<div key={d.name} style={{ width: `${d.pct}%`, background: 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>
|
||||
<div key={d.name} style={{ display: 'flex', alignItems: 'center', gap: '4mm', padding: '2.5mm 0', borderBottom: `1px solid ${COLORS.slate100}` }}>
|
||||
<div style={{ width: '4mm', height: '4mm', background: d.color, flexShrink: 0, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
|
||||
<span style={{ fontSize: '10pt', 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>
|
||||
|
||||
{/* 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,368 @@
|
||||
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 tagline = de ? (company?.tagline_de || 'Kontinuierliche Compliance für europäische Unternehmen.') : (company?.tagline_en || 'Continuous compliance for European companies.')
|
||||
return (
|
||||
<div className="print-page-break">
|
||||
<div className="print-page" style={{ width: '297mm', height: '210mm', background: '#ffffff', color: COLORS.slate900, fontFamily: "'Plus Jakarta Sans', system-ui, sans-serif", display: 'flex', flexDirection: 'column', boxSizing: 'border-box', padding: '14mm', margin: '0 auto 24px', boxShadow: '0 4px 24px rgba(15,23,42,0.10)', overflow: 'hidden' }}>
|
||||
{/* TOP META */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', borderBottom: `1px solid ${COLORS.slate200}`, paddingBottom: '3mm' }}>
|
||||
<span style={{ fontSize: '8pt', fontWeight: 700, letterSpacing: '0.16em', color: COLORS.indigo600, textTransform: 'uppercase' }}>BreakPilot · Investor Brief</span>
|
||||
<span style={{ fontSize: '8pt', color: COLORS.slate500, fontWeight: 500 }}>{versionName}</span>
|
||||
</div>
|
||||
|
||||
{/* HERO */}
|
||||
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center', paddingTop: '8mm' }}>
|
||||
<p style={{ fontSize: '10pt', fontWeight: 700, color: COLORS.indigo600, textTransform: 'uppercase', letterSpacing: '0.16em', margin: 0 }}>
|
||||
{instrument} · Q4 2026
|
||||
</p>
|
||||
<h1 style={{ fontSize: '72pt', fontWeight: 800, color: COLORS.slate900, lineHeight: 0.95, letterSpacing: '-0.025em', margin: '6mm 0 4mm' }}>
|
||||
{company?.name || 'BreakPilot'}<span style={{ color: COLORS.indigo600 }}>.</span>
|
||||
</h1>
|
||||
<p style={{ fontSize: '14pt', fontWeight: 500, color: COLORS.slate700, lineHeight: 1.35, maxWidth: '180mm', margin: 0, letterSpacing: '-0.005em' }}>
|
||||
{tagline}
|
||||
</p>
|
||||
<div style={{ marginTop: '14mm', height: '1px', background: COLORS.slate200, maxWidth: '120mm' }} />
|
||||
<p style={{ fontSize: '11pt', color: COLORS.slate600, marginTop: '6mm', maxWidth: '170mm', lineHeight: 1.5, fontWeight: 400 }}>
|
||||
{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.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* META GRID */}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '8mm', borderTop: `1px solid ${COLORS.slate200}`, borderBottom: `1px solid ${COLORS.slate200}`, padding: '5mm 0', marginBottom: '5mm' }}>
|
||||
{([
|
||||
[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: '7.5pt', color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.1em', margin: 0, fontWeight: 600 }}>{label}</p>
|
||||
<p style={{ fontSize: '14pt', fontWeight: 700, color: COLORS.slate900, margin: '2mm 0 0', fontVariantNumeric: 'tabular-nums' }}>{val}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* FOOTER */}
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '7.5pt', color: COLORS.slate500 }}>
|
||||
<span>{de ? 'Vertraulich, Nur für Investoren' : 'Confidential, For Investor Use Only'}</span>
|
||||
<span style={{ fontWeight: 700, letterSpacing: '0.14em', color: COLORS.indigo600 }}>CONFIDENTIAL</span>
|
||||
</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', '50–200 Kunden in reg. Branchen'] : ['Channel via IT integrators', 'Chamber of Commerce, fairs', 'Content marketing + webinars', '50–200 customers in reg. sectors'] },
|
||||
{ t: de ? 'Phase 3 · Expansion (2028+)' : 'Phase 3 · Expansion (2028+)', tone: COLORS.emerald600, items: de ? ['Enterprise (50–500 MA)', 'EU-Expansion (AT, CH, Benelux)', 'Distributor-Partnerschaften', 'Break-Even Q3 / 2029'] : ['Enterprise (50–500 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 (10–250)' : 'Professional (10–250)', de ? '15–40k €/J' : '€15–40k/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. Wer auf US-KI verzichtet, verliert den Anschluss. Wer sie nutzt, riskiert seine Datensouveränität.', cite: 'Bitkom Cloud Monitor 2024 · DIHK Digitalisierungsumfrage 2024' },
|
||||
{ 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. Das Schrems-II-Urteil des EuGH hat das bestätigt.', cite: 'EuGH C-311/18 (Schrems II, 2020)' },
|
||||
{ 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. Pentests 15-40k €, CE-Risiko 10-25k €. KMU können das allein nicht mehr stemmen.', cite: 'VDMA Compliance-Kosten Maschinenbau 2024' },
|
||||
]
|
||||
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. Those avoiding US AI fall behind. Those using it risk their data sovereignty.', cite: 'Bitkom Cloud Monitor 2024 · DIHK 2024' },
|
||||
{ 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. The Schrems II ruling by the CJEU confirmed this.', cite: 'CJEU C-311/18 (Schrems II, 2020)' },
|
||||
{ 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. Pentests €15-40k, CE risk €10-25k. SMEs can no longer handle this alone.', cite: 'VDMA Compliance Costs Manufacturing 2024' },
|
||||
]
|
||||
|
||||
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, flex: 1 }}>{c.desc}</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' } },
|
||||
{ kicker: 'PILLAR 02', t: 'Compliance auf Autopilot', d: 'VVT, TOMs, DSFA, Löschfristen, CE-Risikobeurteilung automatisch. Nach dem Audit: Abweichungen End-to-End, Rollen, Stichtage, Tickets, Nachweise, Eskalation an GF.', stat: { v: '80%', l: 'Zeitersparnis bei Compliance-Prüfungen' } },
|
||||
{ kicker: 'PILLAR 03', t: 'Deutsche Cloud, volle Integration', d: 'BSI-zertifizierte Cloud in Deutschland. 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' } },
|
||||
]
|
||||
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 as tickets in the issue tracker of your choice, with implementation suggestions.', stat: { v: '€15k+', l: 'pentest costs saved / app / year' } },
|
||||
{ kicker: 'PILLAR 02', t: 'Compliance on Autopilot', d: 'RoPA, TOMs, DPIA, retention policies, CE risk assessment automatically. Post-audit: deviations end-to-end, roles, deadlines, tickets, evidence, escalation to management.', stat: { v: '80%', l: 'time saved on compliance checks' } },
|
||||
{ kicker: 'PILLAR 03', t: 'German Cloud, Full Integration', d: 'BSI-certified cloud in Germany. 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' } },
|
||||
]
|
||||
|
||||
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: '9pt', color: COLORS.slate700, lineHeight: 1.55, flex: 1 }}>{p.d}</div>
|
||||
<div style={{ marginTop: '5mm', 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,359 @@
|
||||
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 ===== */
|
||||
|
||||
export const COLORS = {
|
||||
slate900: '#0f172a',
|
||||
slate800: '#1e293b',
|
||||
slate700: '#334155',
|
||||
slate600: '#475569',
|
||||
slate500: '#64748b',
|
||||
slate400: '#94a3b8',
|
||||
slate300: '#cbd5e1',
|
||||
slate200: '#e2e8f0',
|
||||
slate100: '#f1f5f9',
|
||||
slate50: '#f8fafc',
|
||||
indigo700: '#4338ca',
|
||||
indigo600: '#4f46e5',
|
||||
indigo500: '#6366f1',
|
||||
indigo50: '#eef2ff',
|
||||
emerald700: '#047857',
|
||||
emerald600: '#059669',
|
||||
emerald50: '#ecfdf5',
|
||||
red700: '#b91c1c',
|
||||
red600: '#dc2626',
|
||||
red50: '#fef2f2',
|
||||
amber700: '#b45309',
|
||||
amber600: '#d97706',
|
||||
amber50: '#fffbeb',
|
||||
// legacy aliases for migrating callers
|
||||
dark: '#0f172a',
|
||||
med: '#334155',
|
||||
light: '#64748b',
|
||||
border: '#e2e8f0',
|
||||
indigo: '#4f46e5',
|
||||
indigoLight: '#eef2ff',
|
||||
}
|
||||
|
||||
const FONT = "'Plus Jakarta Sans', 'Inter', system-ui, -apple-system, sans-serif"
|
||||
|
||||
/* ===== 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" style={{
|
||||
width: '297mm',
|
||||
height: '210mm',
|
||||
background: '#ffffff',
|
||||
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(15,23,42,0.10)',
|
||||
WebkitPrintColorAdjust: 'exact',
|
||||
printColorAdjust: 'exact',
|
||||
}}>
|
||||
{/* TITLE BLOCK */}
|
||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '8mm', borderLeft: `3px solid ${COLORS.indigo600}`, paddingLeft: '7mm', marginBottom: '6mm', flexShrink: 0 }}>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'baseline', gap: '4mm', marginBottom: '2mm' }}>
|
||||
<span style={{ fontSize: '8pt', fontWeight: 700, letterSpacing: '0.14em', color: COLORS.indigo600, textTransform: 'uppercase' }}>
|
||||
{kicker} · {section}
|
||||
</span>
|
||||
<span style={{ flex: 1, height: '1px', background: COLORS.slate200, alignSelf: 'center' }} />
|
||||
<span style={{ fontSize: '7.5pt', color: COLORS.slate500, fontWeight: 500 }}>BreakPilot · {versionName}</span>
|
||||
</div>
|
||||
<h1 style={{ fontSize: '22pt', fontWeight: 800, color: COLORS.slate900, margin: 0, lineHeight: 1.1, letterSpacing: '-0.01em' }}>
|
||||
{title}
|
||||
</h1>
|
||||
{subtitle && (
|
||||
<p style={{ fontSize: '10pt', color: COLORS.slate600, margin: '2mm 0 0', fontWeight: 400, lineHeight: 1.4, maxWidth: '210mm' }}>
|
||||
{subtitle}
|
||||
</p>
|
||||
)}
|
||||
</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 */}
|
||||
<div style={{ flexShrink: 0, marginTop: '3mm', paddingTop: '2mm', borderTop: `1px solid ${COLORS.slate200}`, display: 'flex', alignItems: 'center', justifyContent: 'space-between', fontSize: '7.5pt', color: COLORS.slate500 }}>
|
||||
<span>BreakPilot · {versionName}</span>
|
||||
<span style={{ fontWeight: 700, letterSpacing: '0.12em', color: COLORS.slate600 }}>CONFIDENTIAL</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 }}>●</span>
|
||||
if (v === 'partial') return <span style={{ color: COLORS.amber700 }}>◑</span>
|
||||
return <span style={{ color: COLORS.slate300 }}>—</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 +364,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 +395,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,393 @@
|
||||
import { Language, PitchMarket, PitchTeamMember, PitchMilestone, PitchFunding } from '@/lib/types'
|
||||
import { Page, TwoCol, Bullets, Callout, COLORS, DataTable, StatLine } from './PrintLayout'
|
||||
|
||||
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'}>
|
||||
|
||||
{/* TAM/SAM/SOM as nested rectangles */}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1.4fr 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 */}
|
||||
{tam && (
|
||||
<div style={{ border: `1px solid ${COLORS.slate300}`, padding: '4mm', marginBottom: '3mm' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: '1.5mm' }}>
|
||||
<span style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.12em' }}>TAM · {de ? 'Total Addressable Market' : 'Total Addressable Market'}</span>
|
||||
<span style={{ fontSize: '7.5pt', color: COLORS.slate500 }}>+{tam.growth_rate_pct ?? 14}% p.a.</span>
|
||||
</div>
|
||||
<div style={{ fontSize: '28pt', fontWeight: 800, color: COLORS.slate900, lineHeight: 1, fontVariantNumeric: 'tabular-nums', letterSpacing: '-0.02em' }}>{fmtEur(tam.value_eur, de)}</div>
|
||||
<div style={{ fontSize: '8pt', color: COLORS.slate600, marginTop: '2mm', lineHeight: 1.4 }}>{de ? 'Globaler Compliance- und GRC-Markt (alle Branchen, alle Größen).' : 'Global compliance and GRC market (all industries, all sizes).'}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* SAM */}
|
||||
{sam && (
|
||||
<div style={{ border: `1px solid ${COLORS.indigo600}`, background: COLORS.indigo50, padding: '4mm', marginBottom: '3mm', marginLeft: '6mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: '1.5mm' }}>
|
||||
<span style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.indigo700, textTransform: 'uppercase', letterSpacing: '0.12em' }}>SAM · {de ? 'Serviceable Addressable' : 'Serviceable Addressable'}</span>
|
||||
<span style={{ fontSize: '7.5pt', color: COLORS.indigo700 }}>+{sam.growth_rate_pct ?? 18}% p.a.</span>
|
||||
</div>
|
||||
<div style={{ fontSize: '26pt', fontWeight: 800, color: COLORS.indigo700, lineHeight: 1, fontVariantNumeric: 'tabular-nums', letterSpacing: '-0.02em' }}>{fmtEur(sam.value_eur, de)}</div>
|
||||
<div style={{ fontSize: '8pt', color: COLORS.slate700, marginTop: '2mm', lineHeight: 1.4 }}>{de ? 'DACH + EU: regulierte Branchen, KMU + Enterprise.' : 'DACH + EU: regulated industries, SMB + enterprise.'}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* SOM */}
|
||||
{som && (
|
||||
<div style={{ border: `2px solid ${COLORS.emerald600}`, background: COLORS.emerald50, padding: '4mm', marginLeft: '12mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: '1.5mm' }}>
|
||||
<span style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.emerald700, textTransform: 'uppercase', letterSpacing: '0.12em' }}>SOM · {de ? 'Kernmarkt 5 Jahre' : 'Core market 5 yrs'}</span>
|
||||
<span style={{ fontSize: '7.5pt', color: COLORS.emerald700 }}>+{som.growth_rate_pct ?? 25}% p.a.</span>
|
||||
</div>
|
||||
<div style={{ fontSize: '24pt', fontWeight: 800, color: COLORS.emerald700, lineHeight: 1, fontVariantNumeric: 'tabular-nums', letterSpacing: '-0.02em' }}>{fmtEur(som.value_eur, de)}</div>
|
||||
<div style={{ fontSize: '8pt', color: COLORS.slate700, marginTop: '2mm', lineHeight: 1.4 }}>{de ? 'Anlagen- und Maschinenbau DACH, unser Kernsegment.' : 'Machine & plant manufacturing DACH, our core segment.'}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Segment context */}
|
||||
<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 10–500 MA (Zielgröße)' : 'Of which 10–500 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 ? '€50–150k / Jahr' : '€50–150k / 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} · {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}>
|
||||
|
||||
<TwoCol ratio="1:1" gap="8mm" left={
|
||||
<div style={{ borderLeft: `2px solid ${COLORS.indigo600}`, paddingLeft: '5mm', height: '100%', 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' }}>CO-FOUNDER · 01 / 02</span>
|
||||
<span style={{ fontSize: '7.5pt', color: COLORS.slate500, fontWeight: 600 }}>Equity {members[0]?.equity_pct ?? 37.3}%</span>
|
||||
</div>
|
||||
<div style={{ fontSize: '18pt', fontWeight: 800, color: COLORS.slate900, lineHeight: 1.1, marginBottom: '1mm', letterSpacing: '-0.01em' }}>{members[0]?.name || 'Benjamin Bönisch'}</div>
|
||||
<div style={{ fontSize: '10pt', fontWeight: 600, color: COLORS.indigo600, marginBottom: '3mm' }}>{de ? (members[0]?.role_de || 'CEO & Co-Founder') : (members[0]?.role_en || 'CEO & Co-Founder')}</div>
|
||||
<div style={{ fontSize: '9pt', color: COLORS.slate700, lineHeight: 1.55, flex: 1 }}>{de ? (members[0]?.bio_de) : (members[0]?.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.08em', marginBottom: '2mm' }}>{de ? 'Expertise' : 'Expertise'}</div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '1.5mm' }}>
|
||||
{(members[0]?.expertise || []).map((e, i) => (
|
||||
<span key={i} style={{ fontSize: '8pt', padding: '0.5mm 2mm', border: `1px solid ${COLORS.slate300}`, color: COLORS.slate700 }}>{e}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
} right={
|
||||
<div style={{ borderLeft: `2px solid ${COLORS.indigo600}`, paddingLeft: '5mm', height: '100%', 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' }}>CO-FOUNDER · 02 / 02</span>
|
||||
<span style={{ fontSize: '7.5pt', color: COLORS.slate500, fontWeight: 600 }}>Equity {members[1]?.equity_pct ?? 37.3}%</span>
|
||||
</div>
|
||||
<div style={{ fontSize: '18pt', fontWeight: 800, color: COLORS.slate900, lineHeight: 1.1, marginBottom: '1mm', letterSpacing: '-0.01em' }}>{members[1]?.name || 'Sharang Parnerkar'}</div>
|
||||
<div style={{ fontSize: '10pt', fontWeight: 600, color: COLORS.indigo600, marginBottom: '3mm' }}>{de ? (members[1]?.role_de || 'CTO & Co-Founder') : (members[1]?.role_en || 'CTO & Co-Founder')}</div>
|
||||
<div style={{ fontSize: '9pt', color: COLORS.slate700, lineHeight: 1.55, flex: 1 }}>{de ? (members[1]?.bio_de) : (members[1]?.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.08em', marginBottom: '2mm' }}>{de ? 'Expertise' : 'Expertise'}</div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '1.5mm' }}>
|
||||
{(members[1]?.expertise || []).map((e, i) => (
|
||||
<span key={i} style={{ fontSize: '8pt', padding: '0.5mm 2mm', border: `1px solid ${COLORS.slate300}`, color: COLORS.slate700 }}>{e}</span>
|
||||
))}
|
||||
</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 ===== */
|
||||
|
||||
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 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 ? `Pre-Seed: ${fmtEur(amount, true)} via ${instrument}.` : `Pre-Seed: ${fmtEur(amount, false)} 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' }}>
|
||||
€{(amount / 1_000_000).toFixed(1)}M
|
||||
</div>
|
||||
<div style={{ fontSize: '10pt', color: COLORS.slate600, marginTop: '3mm' }}>{instrument} · {funding?.round_name || 'Pre-Seed'} · {de ? 'Zielabschluss' : 'Target close'}: {funding?.target_date || 'Q3 2026'}</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '4mm' }}>
|
||||
{[
|
||||
{ l: de ? 'Pre-Money' : 'Pre-money', v: '€4.0M' },
|
||||
{ l: de ? 'Post-Money' : 'Post-money', v: '€5.0M' },
|
||||
{ l: de ? 'Investor-Anteil' : 'Investor share', v: '20%' },
|
||||
].map((k, 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 }}>{k.l}</div>
|
||||
<div style={{ fontSize: '16pt', fontWeight: 800, color: COLORS.slate900, marginTop: '1mm', fontVariantNumeric: 'tabular-nums' }}>{k.v}</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 · 50–100 Kunden onboarded · ARR €1,5–2,5M in 18 Monaten · Vorbereitung Series A.'
|
||||
: '18-month runway · GmbH incorporated · engineering team scaled to 5 · 50–100 customers onboarded · ARR €1.5–2.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>
|
||||
{/* Horizontal bar */}
|
||||
<div style={{ display: 'flex', height: '8mm', border: `1px solid ${COLORS.slate300}`, marginBottom: '5mm' }}>
|
||||
{useOfFunds.map((u, i) => {
|
||||
const colors = [COLORS.indigo600, COLORS.indigo500, COLORS.amber600, COLORS.slate600, COLORS.slate400]
|
||||
return (
|
||||
<div key={i} style={{ width: `${u.percentage}%`, background: colors[i % colors.length], WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{useOfFunds.map((u, i) => {
|
||||
const colors = [COLORS.indigo600, COLORS.indigo500, COLORS.amber600, COLORS.slate600, COLORS.slate400]
|
||||
return (
|
||||
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: '3mm', padding: '2.5mm 0', borderBottom: `1px solid ${COLORS.slate100}` }}>
|
||||
<div style={{ width: '3mm', height: '3mm', background: colors[i % colors.length], flexShrink: 0, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
|
||||
<div style={{ flex: 1, fontSize: '9pt', 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' }}>{u.percentage}%</div>
|
||||
<div style={{ fontSize: '8pt', color: COLORS.slate500, fontVariantNumeric: 'tabular-nums', minWidth: '14mm', textAlign: 'right' }}>€{(amount * u.percentage / 100 / 1000).toFixed(0)}k</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
/* ===== CUSTOMER SAVINGS ===== */
|
||||
|
||||
export function PrintCustomerSavingsPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
|
||||
const de = lang === 'de'
|
||||
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 der Einsparungen für ein typisches KMU (50 Mitarbeiter) im ersten Jahr.' : 'Detailed savings breakdown for a typical SME (50 employees) in year one.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8mm', flex: 1, minHeight: 0 }}>
|
||||
<div>
|
||||
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '2mm' }}>{de ? 'Was der Kunde heute bezahlt (ohne BreakPilot)' : 'What customer pays today (without BreakPilot)'}</div>
|
||||
<DataTable
|
||||
cols={[
|
||||
{ header: de ? 'Position' : 'Item' },
|
||||
{ header: de ? 'Heute' : 'Today', numeric: true, width: '22%' },
|
||||
{ header: de ? 'Frequenz' : 'Frequency', width: '24%' },
|
||||
]}
|
||||
rows={[
|
||||
[de ? 'Externe Pentests' : 'External pentests', '€15.000', de ? 'jährlich' : 'annually'],
|
||||
[de ? 'CE-Software-Risikobeurteilung' : 'CE software risk assessment', '€12.000', de ? 'pro Produkt' : 'per product'],
|
||||
[de ? 'Compliance-Beauftragte/r (anteilig)' : 'Compliance officer (pro-rated)', '€18.000', de ? 'jährlich' : 'annually'],
|
||||
[de ? 'Audit-Vorbereitung extern' : 'External audit prep', '€9.000', de ? 'jährlich' : 'annually'],
|
||||
[de ? 'Rechtsanwälte (DSGVO, AI Act)' : 'Lawyers (GDPR, AI Act)', '€8.000', de ? 'jährlich' : 'annually'],
|
||||
[de ? 'Auditmanager-Software' : 'Audit manager software', '€5.000', de ? 'jährlich' : 'annually'],
|
||||
[de ? 'Schulungen extern' : 'External training', '€4.000', de ? 'jährlich' : 'annually'],
|
||||
[<strong key="sum">{de ? 'Summe heute' : 'Today total'}</strong>, <strong key="v">€71.000</strong>, ''],
|
||||
]}
|
||||
dense
|
||||
/>
|
||||
|
||||
<div style={{ marginTop: '4mm' }}>
|
||||
<Callout tone="negative" label={de ? 'Versteckte Kosten' : 'Hidden costs'}>
|
||||
{de
|
||||
? 'Zeit der GF + Compliance-Beauftragten (~30 Tage/Jahr), DSGVO-Bußgelder bei Fehlern (bis zu 4% Jahresumsatz), verlorene RFQs durch fehlende Compliance-Nachweise.'
|
||||
: 'Time of management + compliance officer (~30 days/year), GDPR fines on errors (up to 4% annual revenue), lost RFQs due to missing compliance evidence.'}
|
||||
</Callout>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '2mm' }}>{de ? 'Mit BreakPilot' : 'With BreakPilot'}</div>
|
||||
<DataTable
|
||||
cols={[
|
||||
{ header: de ? 'Position' : 'Item' },
|
||||
{ header: de ? 'Mit BP' : 'With BP', numeric: true, width: '22%' },
|
||||
{ header: de ? 'Ersparnis' : 'Savings', numeric: true, width: '22%' },
|
||||
]}
|
||||
rows={[
|
||||
[de ? 'Pentests (kontinuierlich)' : 'Pentests (continuous)', de ? 'inklusive' : 'included', '€13.000'],
|
||||
[de ? 'CE-Risiko (Code-Basis)' : 'CE risk (code-based)', de ? 'inklusive' : 'included', '€9.000'],
|
||||
[de ? 'Compliance-Zeit' : 'Compliance time', '–80%', '€15.000'],
|
||||
[de ? 'Audit-Vorbereitung' : 'Audit prep', de ? 'auf Knopfdruck' : 'one-click', '€9.000'],
|
||||
[de ? 'Legal-Stunden' : 'Legal hours', '–60%', '€5.000'],
|
||||
[de ? 'Schulungen (Academy)' : 'Training (Academy)', de ? 'inklusive' : 'included', '€4.000'],
|
||||
[<strong key="cost">{de ? 'BreakPilot Pro' : 'BreakPilot Pro'}</strong>, <strong key="bp">€25.000</strong>, ''],
|
||||
[<strong key="ts">{de ? 'Gesamt-Ersparnis' : 'Total savings'}</strong>, '', <strong key="v" style={{ color: COLORS.emerald700 }}>€55.000</strong>],
|
||||
]}
|
||||
dense
|
||||
/>
|
||||
|
||||
<div style={{ marginTop: '4mm', padding: '4mm', background: COLORS.emerald50, border: `1px solid ${COLORS.emerald600}`, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
|
||||
<div style={{ fontSize: '7.5pt', color: COLORS.emerald700, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em' }}>{de ? 'Netto-Effekt Jahr 1' : 'Net effect year 1'}</div>
|
||||
<div style={{ display: 'flex', alignItems: 'baseline', gap: '4mm', marginTop: '2mm' }}>
|
||||
<div style={{ fontSize: '32pt', fontWeight: 800, color: COLORS.emerald700, lineHeight: 1, fontVariantNumeric: 'tabular-nums' }}>+€30k</div>
|
||||
<div style={{ fontSize: '11pt', color: COLORS.emerald700, fontWeight: 600 }}>{de ? 'pro KMU / Jahr' : 'per SME / year'}</div>
|
||||
</div>
|
||||
<div style={{ fontSize: '8pt', color: COLORS.slate700, marginTop: '2mm', lineHeight: 1.4 }}>{de ? 'Kunde spart €55k, zahlt €25k. ROI ab Tag 1. Zusätzlich: keine Bußgelder, RFQ-Win-Rate ↑, Schlaf-Frieden für die GF.' : 'Customer saves €55k, pays €25k. ROI from day 1. Plus: no fines, RFQ win-rate ↑, peace of mind for management.'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
import { Language, PitchProduct } from '@/lib/types'
|
||||
import { Page, TwoCol, ThreeCol, FourCol, Bullets, Callout, COLORS, Divider, DataTable, StatLine } from './PrintLayout'
|
||||
import { getDetails } from '@/components/slides/USPSlide.data'
|
||||
|
||||
interface SlideBase { lang: Language; pageNum: number; totalPages: number; versionName: string }
|
||||
|
||||
/* ===== 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: '6mm', flex: 1, minHeight: 0 }}>
|
||||
{pillars.map((k, i) => {
|
||||
const p = d[k]
|
||||
return (
|
||||
<div key={k} style={{ borderLeft: `2px solid ${COLORS.indigo600}`, paddingLeft: '5mm', display: 'flex', flexDirection: 'column', minHeight: 0 }}>
|
||||
<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' }}>{p.kicker}</span>
|
||||
<span style={{ fontSize: '7.5pt', color: COLORS.slate400, fontVariantNumeric: 'tabular-nums' }}>{String(i + 1).padStart(2, '0')} / 04</span>
|
||||
</div>
|
||||
<div style={{ fontSize: '13pt', fontWeight: 700, color: COLORS.slate900, lineHeight: 1.2, letterSpacing: '-0.005em', marginBottom: '3mm' }}>{p.title}</div>
|
||||
<div style={{ fontSize: '8.5pt', color: COLORS.slate700, lineHeight: 1.5, marginBottom: '3mm' }}>{p.body}</div>
|
||||
{p.bullets && <Bullets dense items={p.bullets} />}
|
||||
{p.stat && (
|
||||
<div style={{ marginTop: 'auto', paddingTop: '3mm', borderTop: `1px solid ${COLORS.slate200}`, display: 'flex', alignItems: 'baseline', justifyContent: 'space-between' }}>
|
||||
<span style={{ fontSize: '7.5pt', color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.08em', fontWeight: 600 }}>{p.stat.k}</span>
|
||||
<span style={{ fontSize: '11pt', fontWeight: 800, color: COLORS.emerald700, fontVariantNumeric: 'tabular-nums' }}>{p.stat.v}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
/* ===== USP, PAGE 2 (under the hood + closing loop) ===== */
|
||||
|
||||
export function PrintUSPPage2({ lang, pageNum, totalPages, versionName }: SlideBase) {
|
||||
const de = lang === 'de'
|
||||
const d = getDetails(de)
|
||||
const cards = ['trace', 'engine', 'opt', 'stack'] as const
|
||||
|
||||
return (
|
||||
<Page kicker="05" section={de ? 'USP · 2 / 2' : 'USP · 2 / 2'} title={de ? 'Under the Hood, was die Plattform technisch trägt.' : 'Under the Hood, what the platform is built on.'} subtitle={de ? 'Vier technische Differentiator: Traceability, Continuous Engine, Compliance Optimizer, EU-Trust Stack.' : 'Four technical differentiators: traceability, continuous engine, compliance optimizer, EU trust stack.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6mm' }}>
|
||||
{cards.map(k => {
|
||||
const p = d[k]
|
||||
return (
|
||||
<div key={k} style={{ borderLeft: `2px solid ${COLORS.amber600}`, paddingLeft: '5mm', display: 'flex', flexDirection: 'column' }}>
|
||||
<div style={{ fontSize: '7.5pt', fontWeight: 700, color: COLORS.amber700, textTransform: 'uppercase', letterSpacing: '0.12em', marginBottom: '2mm' }}>{p.kicker}</div>
|
||||
<div style={{ fontSize: '12pt', fontWeight: 700, color: COLORS.slate900, lineHeight: 1.2, marginBottom: '3mm' }}>{p.title}</div>
|
||||
<div style={{ fontSize: '8.5pt', color: COLORS.slate700, lineHeight: 1.5, marginBottom: '3mm' }}>{p.body}</div>
|
||||
{p.bullets && <Bullets dense items={p.bullets} />}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: '6mm' }}>
|
||||
<Callout tone="accent" label={de ? 'Die Schleife' : 'The Loop'}>
|
||||
<div style={{ fontSize: '11pt', fontWeight: 700, color: COLORS.slate900, marginBottom: '2mm' }}>{d.hub.title}</div>
|
||||
<div>{d.hub.body}</div>
|
||||
{d.hub.bullets && (
|
||||
<div style={{ marginTop: '3mm' }}><Bullets dense tone="accent" items={d.hub.bullets} /></div>
|
||||
)}
|
||||
</Callout>
|
||||
</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 MODULES_FULL_DE = [
|
||||
{ name: 'Code Security', icon: '◇', desc: 'SAST · DAST · SBOM · Container · Secrets · Pentesting', features: ['Bei jedem Push', 'Auto-Fix LLM', 'CI/CD-integriert'] },
|
||||
{ name: 'CE-SW-Risikobeurteilung', icon: '◇', desc: 'CE-Kennzeichnung für Maschinen mit Software-Anteil', features: ['Maschinen-VO', 'CRA-konform', 'Code-Basis-Analyse'] },
|
||||
{ name: 'Compliance-Dokumente', icon: '◇', desc: 'VVT (Art. 30) · TOMs · DSFA (Art. 35) · Löschkonzept', features: ['Auto-Generiert', 'Versionsverlauf', 'Audit-tauglich'] },
|
||||
{ name: 'Audit Manager', icon: '◇', desc: 'Abweichungen End-to-End: Rollen · Stichtage · Eskalation', features: ['Tickets + Nachweise', 'GF-Eskalation', 'Compliance-SLA'] },
|
||||
{ name: 'DSR / Betroffenenrechte', icon: '◇', desc: 'Auskunft, Berichtigung, Löschung, Datenübertragbarkeit', features: ['Self-Service', 'Identitätsprüfung', 'Frist-Tracking'] },
|
||||
{ name: 'Consent', icon: '◇', desc: 'Einwilligungs-Management, Cookie-Banner, ePrivacy', features: ['CMP integriert', 'Audit-Log', 'Multi-Tenant'] },
|
||||
{ name: 'Incident Response', icon: '◇', desc: 'Vorfälle, Meldung (72h), Mitigation, Forensik', features: ['Art. 33/34 DSGVO', 'BSI-Meldepfade', 'Forensik-Hooks'] },
|
||||
{ name: 'Compliance LLM', icon: '◇', desc: 'GPT für Text + Audio, EU-gehostet, mit Quellenangabe', features: ['Self-Hosted', 'EU-souverän', 'Audit-zitierbar'] },
|
||||
{ name: 'Tender Matching', icon: '◇', desc: 'RFQ-Antworten automatisch gegen Codebase + Policies', features: ['Stunden statt Wochen', 'Win-ready', 'Klausel-Mapping'] },
|
||||
{ name: 'Academy', icon: '◇', desc: 'Online-Schulungen für Geschäftsführung und Mitarbeiter', features: ['Mandatory Training', 'Zertifikate', 'GF-Pflicht erfüllt'] },
|
||||
{ name: 'Compliance Optimizer', icon: '◇', desc: 'Maximale KI-Nutzung im legalen Rahmen, ersetzt 20-200k € Anwaltskosten', features: ['ROI-ranking', 'Sweet-Spot', 'Risikobalance'] },
|
||||
{ name: 'Kommunikation', icon: '◇', desc: 'Chat (Matrix) + Video (Jitsi) + KI-Support', features: ['Self-Hosted', 'EU-Hosting', 'Audit-Logs'] },
|
||||
]
|
||||
const MODULES_FULL_EN = [
|
||||
{ name: 'Code Security', icon: '◇', desc: 'SAST · DAST · SBOM · Container · Secrets · Pentesting', features: ['Every push', 'Auto-fix LLM', 'CI/CD integrated'] },
|
||||
{ name: 'CE SW Risk Assessment', icon: '◇', desc: 'CE marking for machinery with software', features: ['Machinery Reg.', 'CRA-compliant', 'Code-level analysis'] },
|
||||
{ name: 'Compliance Documents', icon: '◇', desc: 'RoPA (Art. 30) · TOMs · DPIA (Art. 35) · Retention', features: ['Auto-generated', 'Version history', 'Audit-ready'] },
|
||||
{ name: 'Audit Manager', icon: '◇', desc: 'Deviations end-to-end: roles · deadlines · escalation', features: ['Tickets + evidence', 'Mgmt escalation', 'Compliance SLA'] },
|
||||
{ name: 'DSR / Data Subject Rights', icon: '◇', desc: 'Access, rectification, erasure, portability', features: ['Self-service', 'Identity check', 'Deadline tracking'] },
|
||||
{ name: 'Consent', icon: '◇', desc: 'Consent mgmt, cookie banner, ePrivacy', features: ['CMP integrated', 'Audit log', 'Multi-tenant'] },
|
||||
{ name: 'Incident Response', icon: '◇', desc: 'Breaches, reporting (72h), mitigation, forensics', features: ['GDPR Art. 33/34', 'BSI channels', 'Forensic hooks'] },
|
||||
{ name: 'Compliance LLM', icon: '◇', desc: 'GPT for text + audio, EU-hosted, with citations', features: ['Self-hosted', 'EU-sovereign', 'Audit-citable'] },
|
||||
{ name: 'Tender Matching', icon: '◇', desc: 'RFQ answers automatically against codebase + policies', features: ['Hours not weeks', 'Win-ready', 'Clause mapping'] },
|
||||
{ name: 'Academy', icon: '◇', desc: 'Online training for management and staff', features: ['Mandatory training', 'Certificates', 'Mgmt duties fulfilled'] },
|
||||
{ name: 'Compliance Optimizer', icon: '◇', desc: 'Max AI use within legal limits, replaces €20-200k legal fees', features: ['ROI ranking', 'Sweet spot', 'Risk balance'] },
|
||||
{ name: 'Communication', icon: '◇', desc: 'Chat (Matrix) + video (Jitsi) + AI support', features: ['Self-hosted', 'EU hosting', 'Audit logs'] },
|
||||
]
|
||||
|
||||
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) => (
|
||||
<div key={i} style={{ border: `1px solid ${COLORS.slate200}`, borderTop: `2px solid ${COLORS.indigo600}`, padding: '3mm 4mm', display: 'flex', flexDirection: 'column' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: '1mm' }}>
|
||||
<div style={{ fontSize: '10pt', fontWeight: 700, color: COLORS.slate900 }}>{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.45, 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 10–250: 15–40k €/J · Enterprise 250+: ab 50k €/J. Mitarbeiterbasiert. Standard: BSI-Cloud DE. Optional: Mac Mini/Studio für absolute Privacy bei Kleinstunternehmen.'
|
||||
: 'Starter <10 emp: €3,600/yr · Professional 10–250: €15–40k/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.' },
|
||||
]
|
||||
|
||||
export function PrintHowItWorksPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
|
||||
const de = lang === 'de'
|
||||
const steps = de ? STEPS_DE : STEPS_EN
|
||||
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}>
|
||||
|
||||
{/* horizontal step flow */}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '6mm', flex: 1, minHeight: 0 }}>
|
||||
{steps.map((s, i) => (
|
||||
<div key={i} style={{ display: 'flex', flexDirection: 'column', position: 'relative' }}>
|
||||
{i < steps.length - 1 && (
|
||||
<div style={{ position: 'absolute', top: '14mm', right: '-4mm', width: '2mm', height: '1px', background: COLORS.slate300 }} />
|
||||
)}
|
||||
<div style={{ fontSize: '32pt', fontWeight: 800, color: COLORS.indigo600, lineHeight: 1, marginBottom: '4mm', fontVariantNumeric: 'tabular-nums' }}>{s.n}</div>
|
||||
<div style={{ fontSize: '13pt', fontWeight: 700, color: COLORS.slate900, lineHeight: 1.2, marginBottom: '3mm', letterSpacing: '-0.005em' }}>{s.t}</div>
|
||||
<div style={{ fontSize: '9pt', color: COLORS.slate700, lineHeight: 1.55, flex: 1 }}>{s.d}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: '6mm', flexShrink: 0 }}>
|
||||
<Callout tone="accent" label={de ? 'Typische Time-to-Value' : 'Typical time-to-value'}>
|
||||
{de
|
||||
? 'Tag 0: Vertrag · Tag 3: Onboarding-Call · Tag 7: erste Repos angebunden · Tag 14: erste automatische VVT/TOMs · Tag 30: audit-ready Status erreicht.'
|
||||
: 'Day 0: contract · Day 3: onboarding call · Day 7: first repos connected · Day 14: first automated RoPA/TOMs · Day 30: audit-ready status achieved.'}
|
||||
</Callout>
|
||||
</div>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
/* ===== BUSINESS MODEL / PRICING ===== */
|
||||
|
||||
export function PrintBusinessModelPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
|
||||
const de = lang === 'de'
|
||||
return (
|
||||
<Page kicker="10" section={de ? 'PRICING & GESCHÄFTSMODELL' : 'PRICING & BUSINESS MODEL'} title={de ? 'Mitarbeiterbasiertes SaaS. Kunden sparen mehr als sie zahlen.' : 'Employee-based SaaS. Customers save more than they pay.'} subtitle={de ? 'Drei Tiers, transparent gepreist. Recurring Revenue. Optional Hardware. ROI ab Tag 1.' : 'Three tiers, transparently priced. Recurring revenue. Optional hardware. ROI from day 1.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '2fr 1fr', gap: '8mm', flex: 1, minHeight: 0 }}>
|
||||
<div>
|
||||
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '2mm' }}>{de ? 'Pricing-Tiers' : 'Pricing tiers'}</div>
|
||||
<DataTable
|
||||
cols={[
|
||||
{ header: 'Tier', width: '22%' },
|
||||
{ header: de ? 'Zielkunde' : 'Target', width: '24%' },
|
||||
{ header: de ? 'Module' : 'Modules' },
|
||||
{ header: de ? 'Preis / Jahr' : 'Price / year', numeric: true, width: '20%' },
|
||||
]}
|
||||
rows={[
|
||||
['Starter', de ? '<10 MA' : '<10 emp.', de ? 'Basis-Module (DSGVO, Audit, DSR)' : 'Basic modules (GDPR, audit, DSR)', '€3.600'],
|
||||
['Professional', '10–250 MA', de ? 'Alle Module + Priority Support' : 'All modules + priority support', '€15.000 – €40.000'],
|
||||
['Enterprise', '250+ MA', de ? 'Custom · SLA · Dedicated CSM' : 'Custom · SLA · dedicated CSM', de ? 'ab €50.000' : 'from €50,000'],
|
||||
]}
|
||||
highlightFirstCol
|
||||
/>
|
||||
|
||||
<div style={{ marginTop: '6mm' }}>
|
||||
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '2mm' }}>{de ? 'Unit Economics (Reifephase)' : 'Unit Economics (mature)'}</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '4mm' }}>
|
||||
{[
|
||||
{ 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' }}>
|
||||
<div style={{ fontSize: '20pt', fontWeight: 800, color: k.tone === 'positive' ? COLORS.emerald700 : COLORS.slate900, lineHeight: 1, fontVariantNumeric: 'tabular-nums' }}>{k.n}</div>
|
||||
<div style={{ fontSize: '7.5pt', color: COLORS.slate500, marginTop: '2mm', textTransform: 'uppercase', letterSpacing: '0.08em', fontWeight: 600 }}>{k.l}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div style={{ fontSize: '8pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '2mm' }}>{de ? 'Was der Kunde zahlt vs. spart (KMU 50 MA, Jahr 1)' : 'What customer pays vs. saves (SME 50 emp., year 1)'}</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '0' }}>
|
||||
<StatLine label={de ? 'Kunde zahlt' : 'Customer pays'} value="€25.000" tone="accent" />
|
||||
<StatLine label={de ? 'Spart: Pentests' : 'Saves: pentests'} value="€13.000" tone="positive" />
|
||||
<StatLine label={de ? 'Spart: CE-Risiko' : 'Saves: CE risk'} value="€9.000" tone="positive" />
|
||||
<StatLine label={de ? 'Spart: Compliance-Zeit' : 'Saves: compliance time'} value="€15.000" tone="positive" />
|
||||
<StatLine label={de ? 'Spart: Audit-Vorber.' : 'Saves: audit prep'} value="€9.000" tone="positive" />
|
||||
<StatLine label={de ? 'Spart: Sonstiges' : 'Saves: other'} value="€9.000" tone="positive" />
|
||||
</div>
|
||||
<div style={{ marginTop: '4mm', padding: '4mm', background: COLORS.emerald50, border: `1px solid ${COLORS.emerald600}`, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
|
||||
<div style={{ fontSize: '7.5pt', color: COLORS.emerald700, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: '1.5mm' }}>{de ? 'Netto-Effekt Jahr 1' : 'Net effect year 1'}</div>
|
||||
<div style={{ fontSize: '20pt', fontWeight: 800, color: COLORS.emerald700, lineHeight: 1, fontVariantNumeric: 'tabular-nums' }}>+€30.000</div>
|
||||
<div style={{ fontSize: '8pt', color: COLORS.slate600, marginTop: '2mm', lineHeight: 1.4 }}>{de ? 'Kunde spart €55k, zahlt €25k. ROI ab Tag 1.' : 'Customer saves €55k, pays €25k. ROI from day 1.'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
@@ -24,12 +24,14 @@
|
||||
min-height: 0 !important;
|
||||
overflow: visible !important;
|
||||
background: #ffffff !important;
|
||||
color: #000000 !important;
|
||||
color: #0f172a !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
font-family: 'Plus Jakarta Sans', 'Inter', 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,6 +43,7 @@
|
||||
margin: 0 !important;
|
||||
display: block !important;
|
||||
overflow: visible !important;
|
||||
font-family: 'Plus Jakarta Sans', 'Inter', system-ui, sans-serif !important;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -73,8 +76,23 @@
|
||||
margin: 0 !important;
|
||||
box-shadow: none !important;
|
||||
background: #ffffff !important;
|
||||
font-family: 'Plus Jakarta Sans', 'Inter', system-ui, sans-serif !important;
|
||||
color: #0f172a !important;
|
||||
-webkit-print-color-adjust: exact;
|
||||
-moz-print-color-adjust: exact;
|
||||
print-color-adjust: exact;
|
||||
}
|
||||
|
||||
/* Tabular numerals everywhere — institutional research aesthetic */
|
||||
.print-page table,
|
||||
.print-page .num,
|
||||
.print-page .kpi {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
}
|
||||
|
||||
/* Screen preview: apply Plus Jakarta Sans to print pages even on screen */
|
||||
.print-page, .print-page-break {
|
||||
font-family: 'Plus Jakarta Sans', 'Inter', system-ui, sans-serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user