feat(pitch-print): TL;DR + Differentiators + KPIs + Tech Stack + P&L promoted
Build pitch-deck / build-push-deploy (push) Successful in 1m43s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 33s
CI / test-python-voice (push) Successful in 31s
CI / test-bqas (push) Successful in 32s
Build pitch-deck / build-push-deploy (push) Successful in 1m43s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 33s
CI / test-python-voice (push) Successful in 31s
CI / test-bqas (push) Successful in 32s
Adds the 5 slides flagged as missing vs Claude Design (30 slides). Standard
PDF now matches Claude's slide count and structure.
New slides (PrintNewSlides.tsx):
- TL;DR / 30 SEKUNDEN — 4 quad cards (Scale / Sovereignty / Bidirectional /
Speed) with mono kicker, hero stat, body and ticker line. Slot 3, after the
exec summary.
- Differentiators — 4 under-the-hood cards (Traceability / Engine / Optimizer
/ EU-Trust-Stack) extracted from USP p2. Slot 9, after USP. Each card has
the lucide icon in a violet/amber tile, full body + bullets, and the mono
ticker line.
- KPIs (Trajektorie 2026 → 2030) — 8 hero tiles showing year-1 → year-5
transitions (ARR, customers, ARPU, employees, gross margin, EBIT, net
income, cash). Derived live from computeAnnualKPIs(fmResults). Slot 23.
- Tech Stack — 8-category grid (Frontend / Backend / Storage / AI-RAG /
Code-Scanning / Auth / Comms / DevOps), each with lucide icon tile +
category label + monospaced tech list. Slot 31, after Engineering.
USP p2 redesigned: now hero-sized closing loop only (the 4 cards moved to
Differentiators). Bigger LoopDiagram in a violet-tinted hero panel, 12mm
inner padding, more room for the hub body + bullets.
P&L Detail (PrintFinancialsPage) promoted from financial-only to standard
PDF. Kicker now 21 (was '17b'), subtitle rewritten ('Annualisierte GuV',
no longer 'Investor-only'). Empty-data fallback added so it doesn't crash
if fmResults isn't populated.
Anhang divider moved from PrintAnnexSlides.tsx to PrintNewSlides.tsx (was
pushing PrintAnnexSlides over the 500-LOC cap). Section list inside the
divider updated for the new numbering — now 12 sections from #18 GTM down
to #29 Glossary.
PrintDeck.tsx: BASE_PAGES bumped 30 → 35. Render order updated; hasFinancialDetail
flag removed (P&L always rendered); cap-table is the only remaining
financial-only conditional and stays suppressed for Wandeldarlehen.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -4,107 +4,8 @@ import { ArchitectureDiagram, PipelineFlow } from './PrintDiagrams'
|
||||
|
||||
interface SlideBase { lang: Language; pageNum: number; totalPages: number; versionName: string }
|
||||
|
||||
/* ===== ANHANG DIVIDER ===== */
|
||||
|
||||
/**
|
||||
* Chapter-break slide that sits between the main pitch and the appendix.
|
||||
* Bypasses the standard Page chrome to render as a hero divider, but keeps
|
||||
* a footer row consistent with all other slides.
|
||||
*/
|
||||
export function PrintAnnexDividerPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
|
||||
const de = lang === 'de'
|
||||
const MONO = "'JetBrains Mono', ui-monospace, monospace"
|
||||
|
||||
const sections: [string, string, string][] = de ? [
|
||||
['17', 'Go-to-Market Strategie', 'Pilot · Skalierung · Expansion'],
|
||||
['18', 'Finanzplan', 'P&L 2026–2030 · KPI-Dashboard'],
|
||||
['19', 'Treibervariablen', 'Annahmen + Sensitivitätsszenarien'],
|
||||
['20', 'Regulatorische Details', 'DSGVO · AI Act · NIS-2 · CRA · MaschVO'],
|
||||
['21', 'Systemarchitektur', '3 Tiers · LiteLLM Gateway · lokale Inferenz'],
|
||||
['22', 'Engineering Deep Dive', '500K+ LoC · 45 Container · 100 % Self-Hosted'],
|
||||
['23', 'KI-Pipeline', 'RAG · Multi-Agent · Document Intelligence · QA'],
|
||||
['24', 'Risiken & Mitigation', '10 Risiken in 5 Kategorien'],
|
||||
['25', 'Glossar', '30 Begriffe · Compliance · Engineering · Recht'],
|
||||
] : [
|
||||
['17', 'Go-to-Market Strategy', 'Pilot · Scale · Expansion'],
|
||||
['18', 'Financial Plan', 'P&L 2026–2030 · KPI dashboard'],
|
||||
['19', 'Driver Variables', 'Assumptions + sensitivity scenarios'],
|
||||
['20', 'Regulatory Details', 'GDPR · AI Act · NIS-2 · CRA · Machinery Reg.'],
|
||||
['21', 'System Architecture', '3 tiers · LiteLLM gateway · local inference'],
|
||||
['22', 'Engineering Deep Dive', '500K+ LoC · 45 containers · 100 % self-hosted'],
|
||||
['23', 'AI Pipeline', 'RAG · multi-agent · document intelligence · QA'],
|
||||
['24', 'Risks & Mitigation', '10 risks across 5 categories'],
|
||||
['25', 'Glossary', '30 terms · compliance · engineering · law'],
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="print-page-break">
|
||||
<div className="print-page print-page-bg" style={{
|
||||
width: '297mm',
|
||||
height: '210mm',
|
||||
color: COLORS.slate900,
|
||||
fontFamily: "'Inter', system-ui, sans-serif",
|
||||
boxSizing: 'border-box',
|
||||
padding: '14mm 18mm',
|
||||
margin: '0 auto 24px',
|
||||
boxShadow: '0 4px 24px rgba(59,26,122,0.10)',
|
||||
overflow: 'hidden',
|
||||
WebkitPrintColorAdjust: 'exact',
|
||||
printColorAdjust: 'exact',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}>
|
||||
|
||||
{/* Top meta row */}
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderBottom: `1px solid ${COLORS.slate200}`, paddingBottom: '3mm' }}>
|
||||
<span style={{ fontFamily: MONO, fontSize: '7.5pt', fontWeight: 700, color: COLORS.violet600, textTransform: 'uppercase', letterSpacing: '0.22em' }}>{de ? 'Teil II · Anhang' : 'Part II · Appendix'}</span>
|
||||
<span style={{ fontFamily: MONO, fontSize: '7pt', color: COLORS.slate500, letterSpacing: '0.16em', textTransform: 'uppercase', fontWeight: 700 }}>BreakPilot · ComplAI</span>
|
||||
</div>
|
||||
|
||||
{/* Hero */}
|
||||
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center', maxWidth: '260mm' }}>
|
||||
<div style={{ fontFamily: MONO, fontSize: '10pt', fontWeight: 700, color: COLORS.violet600, textTransform: 'uppercase', letterSpacing: '0.3em', marginBottom: '6mm' }}>
|
||||
{de ? '16 · Kapitelwechsel' : '16 · Chapter break'}
|
||||
</div>
|
||||
<h1 style={{ fontSize: '74pt', fontWeight: 800, color: COLORS.slate900, lineHeight: 0.95, letterSpacing: '-0.035em', margin: 0 }}>
|
||||
{de ? 'Anhang' : 'Appendix'}<span style={{ color: COLORS.violet600 }}>.</span>
|
||||
</h1>
|
||||
<div style={{ height: '3px', width: '60mm', background: `linear-gradient(90deg, ${COLORS.violet700} 0%, ${COLORS.violet400} 50%, ${COLORS.violet700} 100%)`, marginTop: '6mm', marginBottom: '8mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
|
||||
<p style={{ fontSize: '15pt', fontWeight: 500, color: COLORS.slate700, lineHeight: 1.3, margin: 0, letterSpacing: '-0.008em', maxWidth: '230mm' }}>
|
||||
{de
|
||||
? 'Detailangaben & Belege. Was wir in der Pitch gesagt haben, mit Quellen, Zahlen und Architektur belegt.'
|
||||
: 'Detail & evidence. Everything we claimed in the pitch, backed by sources, numbers and architecture.'}
|
||||
</p>
|
||||
|
||||
{/* What's coming */}
|
||||
<div style={{ marginTop: '12mm' }}>
|
||||
<div style={{ fontFamily: MONO, fontSize: '8pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.18em', marginBottom: '4mm' }}>
|
||||
{de ? 'Auf den folgenden Seiten' : 'On the following pages'}
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '5mm 8mm' }}>
|
||||
{sections.map(([n, t, sub]) => (
|
||||
<div key={n} style={{ display: 'flex', alignItems: 'flex-start', gap: '4mm' }}>
|
||||
<span style={{ fontFamily: MONO, fontSize: '14pt', fontWeight: 800, color: COLORS.violet600, lineHeight: 1, fontVariantNumeric: 'tabular-nums', letterSpacing: '-0.02em', minWidth: '11mm' }}>{n}</span>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ fontSize: '10pt', fontWeight: 700, color: COLORS.slate900, lineHeight: 1.2 }}>{t}</div>
|
||||
<div style={{ fontSize: '7.5pt', color: COLORS.slate600, marginTop: '0.5mm', lineHeight: 1.35 }}>{sub}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer (matches Page primitive) */}
|
||||
<div className="print-mono" style={{ fontFamily: MONO, paddingTop: '3mm', borderTop: `1px solid ${COLORS.slate200}`, display: 'flex', alignItems: 'center', justifyContent: 'space-between', fontSize: '7pt', color: COLORS.slate500, letterSpacing: '0.16em', textTransform: 'uppercase', fontWeight: 700 }}>
|
||||
<span>BreakPilot · ComplAI</span>
|
||||
<span style={{ color: COLORS.violet600 }}>{versionName}</span>
|
||||
<span style={{ fontVariantNumeric: 'tabular-nums' }}>{String(pageNum).padStart(2, '0')} / {String(totalPages).padStart(2, '0')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
/* The Anhang divider lives in PrintNewSlides.tsx so this file stays under
|
||||
* the 500-LOC cap. */
|
||||
|
||||
/* ===== STRATEGY / GO-TO-MARKET ===== */
|
||||
|
||||
|
||||
@@ -18,10 +18,13 @@ import {
|
||||
PrintCompetitionPage1, PrintCompetitionPage2,
|
||||
} from './PrintCompetitionSlides'
|
||||
import {
|
||||
PrintAnnexDividerPage,
|
||||
PrintStrategyPage, PrintRegulatoryPage, PrintArchitecturePage,
|
||||
PrintEngineeringPage, PrintAIPipelinePage, PrintRisksPage, PrintGlossaryPage,
|
||||
} from './PrintAnnexSlides'
|
||||
import {
|
||||
PrintTLDRPage, PrintDifferentiatorsPage, PrintKPIHeroPage,
|
||||
PrintTechStackPage, PrintAnnexDividerPage,
|
||||
} from './PrintNewSlides'
|
||||
import {
|
||||
PrintFinanzplanPage1, PrintFinanzplanPage2, PrintAssumptionsPage,
|
||||
PrintFinancialsPage, PrintCapTablePage, PrintDisclaimerPage,
|
||||
@@ -44,17 +47,21 @@ export default function PrintDeck({ pitchData, versionName, fmResults, fmAssumpt
|
||||
(pitchData.funding?.instrument || '').toLowerCase().includes('convertible')
|
||||
const hasCapTable = financial && !isWandeldarlehen
|
||||
const annualRows = aggregateAnnualRows(fmResults)
|
||||
const hasFinancialDetail = financial && annualRows.length > 0
|
||||
const hasFinancialData = annualRows.length > 0
|
||||
const de = lang === 'de'
|
||||
|
||||
// Base standard PDF: 30 physical pages.
|
||||
// 2 (exec) + 1 (cover) + 1 (problem) + 1 (solution) + 2 (usp) + 1 (regL) +
|
||||
// 1 (product) + 1 (how) + 1 (market) + 1 (bm) + 1 (milestones) + 2 (competition) +
|
||||
// 1 (team) + 1 (ask) + 1 (savings) + 1 (anhang-divider) + 1 (strategy) +
|
||||
// 2 (finanzplan) + 1 (assumptions) + 1 (regulatory) + 1 (architecture) +
|
||||
// 1 (engineering) + 1 (aipipeline) + 1 (risks) + 1 (glossary) + 1 (disclaimer) = 30
|
||||
const BASE_PAGES = 30
|
||||
const totalPages = BASE_PAGES + (hasFinancialDetail ? 1 : 0) + (hasCapTable ? 1 : 0)
|
||||
// Base standard PDF: 35 physical pages.
|
||||
// 2 (exec) + 1 (TL;DR) + 1 (cover) + 1 (problem) + 1 (solution) + 2 (usp) +
|
||||
// 1 (differentiators) + 1 (regL) + 1 (product) + 1 (how) + 1 (market) + 1 (bm) +
|
||||
// 1 (milestones) + 2 (competition) + 1 (team) + 1 (ask) + 1 (savings) +
|
||||
// 1 (anhang-divider) + 1 (strategy) + 1 (kpis) + 2 (finanzplan) + 1 (p&l detail) +
|
||||
// 1 (assumptions) + 1 (regulatory) + 1 (architecture) + 1 (engineering) +
|
||||
// 1 (tech-stack) + 1 (aipipeline) + 1 (risks) + 1 (glossary) + 1 (disclaimer) = 35
|
||||
// P&L detail is now in standard PDF (was financial-only); cap-table stays
|
||||
// financial-only (and is suppressed for Wandeldarlehen).
|
||||
const BASE_PAGES = 35
|
||||
const totalPages = BASE_PAGES + (hasCapTable ? 1 : 0)
|
||||
void hasFinancialData
|
||||
|
||||
useEffect(() => {
|
||||
const t = setTimeout(() => window.print(), 900)
|
||||
@@ -93,96 +100,109 @@ export default function PrintDeck({ pitchData, versionName, fmResults, fmAssumpt
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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 */}
|
||||
<div className="print-deck-wrapper" style={{ padding: '32px 0', fontFamily: "'Inter', system-ui, sans-serif" }}>
|
||||
{/* PITCH (slides 01–17) */}
|
||||
|
||||
{/* 1. executive-summary (2 pages) */}
|
||||
{/* 01–02 executive-summary (2 pages) */}
|
||||
<PrintExecSummaryPage1 market={pitchData.market || []} {...p()} />
|
||||
<PrintExecSummaryPage2 {...p()} />
|
||||
|
||||
{/* 2. cover */}
|
||||
{/* 03 TL;DR — 30 Sekunden */}
|
||||
<PrintTLDRPage {...p()} />
|
||||
|
||||
{/* 04 cover */}
|
||||
{(() => { n += 1; return <PrintCoverPage company={pitchData.company} funding={pitchData.funding} versionName={versionName} lang={lang} /> })()}
|
||||
|
||||
{/* 3. problem */}
|
||||
{/* 05 problem */}
|
||||
<PrintProblemPage {...p()} />
|
||||
|
||||
{/* 4. solution */}
|
||||
{/* 06 solution */}
|
||||
<PrintSolutionPage {...p()} />
|
||||
|
||||
{/* 5. usp (2 pages) */}
|
||||
{/* 07–08 usp (2 pages) */}
|
||||
<PrintUSPPage1 {...p()} />
|
||||
<PrintUSPPage2 {...p()} />
|
||||
|
||||
{/* 6. regulatory-landscape */}
|
||||
{/* 09 differentiators */}
|
||||
<PrintDifferentiatorsPage {...p()} />
|
||||
|
||||
{/* 10 regulatory-landscape */}
|
||||
<PrintRegulatoryLandscapePage {...p()} />
|
||||
|
||||
{/* 7. product / modular-toolkit */}
|
||||
{/* 11 product / modular-toolkit */}
|
||||
<PrintProductPage products={pitchData.products || []} {...p()} />
|
||||
|
||||
{/* 8. how-it-works */}
|
||||
{/* 12 how-it-works */}
|
||||
<PrintHowItWorksPage {...p()} />
|
||||
|
||||
{/* 9. market */}
|
||||
{/* 13 market */}
|
||||
<PrintMarketPage market={pitchData.market || []} {...p()} />
|
||||
|
||||
{/* 10. business-model / pricing */}
|
||||
{/* 14 business-model / pricing */}
|
||||
<PrintBusinessModelPage {...p()} />
|
||||
|
||||
{/* 11. traction (uses milestones) */}
|
||||
{/* 15 traction (milestones) */}
|
||||
<PrintMilestonesPage milestones={pitchData.milestones || []} {...p()} />
|
||||
|
||||
{/* 12. competition (2 pages) */}
|
||||
{/* 16–17 competition (2 pages) */}
|
||||
<PrintCompetitionPage1 {...p()} />
|
||||
<PrintCompetitionPage2 {...p()} />
|
||||
|
||||
{/* 13. team */}
|
||||
{/* 18 team */}
|
||||
<PrintTeamPage team={pitchData.team || []} {...p()} />
|
||||
|
||||
{/* 14. the-ask */}
|
||||
{/* 19 the-ask */}
|
||||
<PrintTheAskPage funding={pitchData.funding} {...p()} />
|
||||
|
||||
{/* 15. customer-savings */}
|
||||
{/* 20 customer-savings */}
|
||||
<PrintCustomerSavingsPage {...p()} />
|
||||
|
||||
{/* 16. ANHANG divider — chapter break before the appendix block */}
|
||||
{/* 21 ANHANG divider — chapter break before the appendix */}
|
||||
<PrintAnnexDividerPage {...p()} />
|
||||
|
||||
{/* 17. annex-strategy */}
|
||||
{/* APPENDIX (slides 22–35) */}
|
||||
|
||||
{/* 22 annex-strategy */}
|
||||
<PrintStrategyPage {...p()} />
|
||||
|
||||
{/* 17. annex-finanzplan (2 pages) */}
|
||||
{/* 23 KPIs — 2026 → 2030 trajectory */}
|
||||
<PrintKPIHeroPage fmResults={fmResults} {...p()} />
|
||||
|
||||
{/* 24–25 annex-finanzplan (2 pages) */}
|
||||
<PrintFinanzplanPage1 fmResults={fmResults} {...p()} />
|
||||
<PrintFinanzplanPage2 fmResults={fmResults} {...p()} />
|
||||
|
||||
{/* Financial-only: detail P&L */}
|
||||
{hasFinancialDetail && <PrintFinancialsPage annualRows={annualRows} {...p()} />}
|
||||
{/* 26 P&L detail (was financial-only; now standard) */}
|
||||
<PrintFinancialsPage annualRows={annualRows} {...p()} />
|
||||
|
||||
{/* 18. annex-assumptions */}
|
||||
{/* 27 annex-assumptions */}
|
||||
<PrintAssumptionsPage assumptions={fmAssumptions} {...p()} />
|
||||
|
||||
{/* 19. annex-regulatory */}
|
||||
{/* 28 annex-regulatory */}
|
||||
<PrintRegulatoryPage {...p()} />
|
||||
|
||||
{/* 20. annex-architecture */}
|
||||
{/* 29 annex-architecture */}
|
||||
<PrintArchitecturePage {...p()} />
|
||||
|
||||
{/* 21. annex-engineering */}
|
||||
{/* 30 annex-engineering */}
|
||||
<PrintEngineeringPage {...p()} />
|
||||
|
||||
{/* 22. annex-aipipeline */}
|
||||
{/* 31 tech-stack */}
|
||||
<PrintTechStackPage {...p()} />
|
||||
|
||||
{/* 32 annex-aipipeline */}
|
||||
<PrintAIPipelinePage {...p()} />
|
||||
|
||||
{/* 23. risks */}
|
||||
{/* 33 risks */}
|
||||
<PrintRisksPage {...p()} />
|
||||
|
||||
{/* 24. annex-glossary */}
|
||||
{/* 34 annex-glossary */}
|
||||
<PrintGlossaryPage {...p()} />
|
||||
|
||||
{/* Financial-only: cap-table (suppressed for Wandeldarlehen) */}
|
||||
{hasCapTable && <PrintCapTablePage {...p()} />}
|
||||
|
||||
{/* 25. legal-disclaimer */}
|
||||
{/* 35 legal-disclaimer */}
|
||||
<PrintDisclaimerPage {...p()} />
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -260,13 +260,20 @@ export function PrintAssumptionsPage({ assumptions, lang, pageNum, totalPages, v
|
||||
)
|
||||
}
|
||||
|
||||
/* ===== FINANCIAL DETAIL (financial-only extra page) ===== */
|
||||
/* ===== P&L DETAIL — now in standard PDF ===== */
|
||||
|
||||
export function PrintFinancialsPage({ annualRows, lang, pageNum, totalPages, versionName }: SlideBase & { annualRows: AnnualPLRow[] }) {
|
||||
const de = lang === 'de'
|
||||
const breakEvenYear = annualRows.find(r => r.ebitda_eur > 0)?.year
|
||||
if (annualRows.length === 0) {
|
||||
return (
|
||||
<Page kicker="21" section={de ? 'ANHANG · P&L DETAIL' : 'APPENDIX · P&L DETAIL'} title={de ? 'P&L Detail nicht verfügbar' : 'P&L detail unavailable'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
|
||||
<p style={{ fontSize: '10pt', color: COLORS.slate600 }}>{de ? 'Keine Finanzdaten vorhanden. Bitte Base-Case-Szenario auswählen.' : 'No financial data available. Please select base-case scenario.'}</p>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Page kicker="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}>
|
||||
<Page kicker="21" section={de ? 'ANHANG · P&L DETAIL' : 'APPENDIX · P&L DETAIL'} title={de ? 'Annualisierte Gewinn- und Verlust-Rechnung.' : 'Annualized profit & loss.'} subtitle={de ? 'Konsolidierte Jahreswerte 2026–2030 in EUR. Klammern () zeigen Aufwendungen. EBITDA in grün ab Break-Even.' : 'Consolidated annual values 2026–2030 in EUR. Parentheses () mark costs. EBITDA in green from break-even on.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
|
||||
|
||||
<DataTable
|
||||
cols={[
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
import { Language, FMResult } from '@/lib/types'
|
||||
import { Page, COLORS, Bullets } from './PrintLayout'
|
||||
import {
|
||||
ScanLine, Shield, Database, Brain, ShieldCheck, Lock, MessageSquare, Wrench,
|
||||
Layers, Sparkles, TrendingUp, Globe,
|
||||
type LucideIcon,
|
||||
} from 'lucide-react'
|
||||
import { getDetails } from '@/components/slides/USPSlide.data'
|
||||
import { computeAnnualKPIs } from '@/lib/finanzplan/annual-kpis'
|
||||
|
||||
interface SlideBase { lang: Language; pageNum: number; totalPages: number; versionName: string }
|
||||
|
||||
const MONO = "'JetBrains Mono', ui-monospace, monospace"
|
||||
|
||||
/* ====================================================================== */
|
||||
/* TL;DR — 02 · 30 Sekunden (4 quad cards) */
|
||||
/* ====================================================================== */
|
||||
|
||||
export function PrintTLDRPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
|
||||
const de = lang === 'de'
|
||||
|
||||
const cards = de ? [
|
||||
{ kicker: '01 · Scale', title: '25.000+ Controls', body: 'Atomare Prüfaspekte über DSGVO, AI Act, NIS-2, CRA, MaschVO und 380+ weitere Quellen.', ticker: 'idx 26.123 atomic checks', tint: COLORS.violet600 },
|
||||
{ kicker: '02 · Sovereignty', title: '100 % EU-souverän', body: 'BSI-C5-zertifizierte Cloud in Deutschland und Frankreich. Keine US-Anbieter. Air-gap-fähig.', ticker: 'region BSI C5 · DE/FR', tint: COLORS.violet500 },
|
||||
{ kicker: '03 · Bidirectional', title: 'Compliance ↔ Code', body: 'Policy-Änderungen fliessen in den Code; Code-Änderungen aktualisieren Policies. Zero Drift.', ticker: 'sync policy.md → controller.ts', tint: COLORS.amber600 },
|
||||
{ kicker: '04 · Speed', title: '<20 Tage audit-ready', body: 'Vom Vertrag zum auditfähigen Status: typischerweise 14–20 Tage. White-Glove-Onboarding.', ticker: 'tag 17 bis audit-ready', tint: COLORS.amber700 },
|
||||
] : [
|
||||
{ kicker: '01 · Scale', title: '25,000+ Controls', body: 'Atomic audit aspects across GDPR, AI Act, NIS-2, CRA, Machinery Reg. and 380+ other sources.', ticker: 'idx 26,123 atomic checks', tint: COLORS.violet600 },
|
||||
{ kicker: '02 · Sovereignty', title: '100 % EU-sovereign', body: 'BSI-C5 certified cloud in Germany and France. No US vendors. Air-gap capable.', ticker: 'region BSI C5 · DE/FR', tint: COLORS.violet500 },
|
||||
{ kicker: '03 · Bidirectional', title: 'Compliance ↔ Code', body: 'Policy edits flow into code; code changes update policies. Zero drift.', ticker: 'sync policy.md → controller.ts', tint: COLORS.amber600 },
|
||||
{ kicker: '04 · Speed', title: '<20 days audit-ready', body: 'From contract to audit-ready: typically 14–20 days. White-glove onboarding.', ticker: 'day 17 to audit-ready', tint: COLORS.amber700 },
|
||||
]
|
||||
|
||||
return (
|
||||
<Page kicker="02" section={de ? '30 SEKUNDEN' : '30 SECONDS'} title={de ? 'Was BreakPilot in einem Satz.' : 'BreakPilot in one sentence.'} subtitle={de ? 'Kontinuierliche Compliance & Security für den industriellen Mittelstand — EU-souverän, Bidirektional, in unter 20 Tagen produktiv.' : 'Continuous compliance & security for the industrial mid-market — EU-sovereign, bidirectional, productive in under 20 days.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gridTemplateRows: '1fr 1fr', gap: '5mm', flex: 1, minHeight: 0 }}>
|
||||
{cards.map((c, i) => (
|
||||
<div key={i} style={{ border: `1px solid ${c.tint}55`, borderTop: `3px solid ${c.tint}`, borderRadius: '4pt', background: `linear-gradient(135deg, ${c.tint}10 0%, #ffffff 100%)`, padding: '5mm 6mm', display: 'flex', flexDirection: 'column', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
|
||||
<div style={{ fontFamily: MONO, fontSize: '8pt', fontWeight: 700, color: c.tint, textTransform: 'uppercase', letterSpacing: '0.22em', marginBottom: '3mm' }}>{c.kicker}</div>
|
||||
<div style={{ fontSize: '22pt', fontWeight: 800, color: COLORS.slate900, lineHeight: 1.05, letterSpacing: '-0.025em', marginBottom: '3mm' }}>{c.title}</div>
|
||||
<div style={{ fontSize: '9pt', color: COLORS.slate700, lineHeight: 1.5, flex: 1 }}>{c.body}</div>
|
||||
<div style={{ marginTop: '3mm', paddingTop: '2mm', borderTop: `1px solid ${COLORS.slate200}`, display: 'flex', alignItems: 'center', gap: '2mm' }}>
|
||||
<span style={{ width: '2mm', height: '2mm', borderRadius: '50%', background: COLORS.emerald600, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
|
||||
<span style={{ fontFamily: MONO, fontSize: '7pt', color: c.tint, fontWeight: 700 }}>{c.ticker}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
/* ====================================================================== */
|
||||
/* DIFFERENTIATORS — 4 under-the-hood cards (moved out of USP p2) */
|
||||
/* ====================================================================== */
|
||||
|
||||
const DIFF_ICON: Record<string, LucideIcon> = {
|
||||
trace: Layers,
|
||||
engine: Sparkles,
|
||||
opt: TrendingUp,
|
||||
stack: Globe,
|
||||
}
|
||||
|
||||
export function PrintDifferentiatorsPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
|
||||
const de = lang === 'de'
|
||||
const d = getDetails(de)
|
||||
const cards = ['trace', 'engine', 'opt', 'stack'] as const
|
||||
const tickers = de
|
||||
? ['trace 13.605 evidence-chain', 'validate 1.450 · 98,9 %', 'optimize gap → policy §4.2', 'check BSI C5 · 1.382']
|
||||
: ['trace 13,605 evidence-chain', 'validate 1,450 · 98.9 %', 'optimize gap → policy §4.2', 'check BSI C5 · 1,382']
|
||||
|
||||
return (
|
||||
<Page kicker="06" section={de ? 'DIFFERENTIATORS' : 'DIFFERENTIATORS'} title={de ? 'Vier technische Differentiator.' : 'Four technical differentiators.'} subtitle={de ? 'Was kein anderer Anbieter geschlossen liefert: Traceability, kontinuierliche Engine, Compliance-Optimizer, EU-Trust-Stack.' : 'What no other vendor delivers end-to-end: traceability, continuous engine, compliance optimizer, EU-trust stack.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '4mm', flex: 1, minHeight: 0 }}>
|
||||
{cards.map((k, i) => {
|
||||
const p = d[k]
|
||||
const Icon = DIFF_ICON[k]
|
||||
const tint = i < 2 ? COLORS.violet600 : COLORS.amber600
|
||||
const tintDark = i < 2 ? COLORS.violet700 : COLORS.amber700
|
||||
const tintLight = i < 2 ? COLORS.violet50 : COLORS.amber50
|
||||
return (
|
||||
<div key={k} style={{ border: `1px solid ${tint}40`, background: `linear-gradient(135deg, ${tintLight} 0%, #ffffff 100%)`, borderRadius: '4pt', padding: '4mm 5mm', display: 'flex', flexDirection: 'column', overflow: 'hidden', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
|
||||
<div style={{ width: '9mm', height: '9mm', background: '#ffffff', borderRadius: '2pt', border: `1px solid ${tint}`, display: 'flex', alignItems: 'center', justifyContent: 'center', color: tint, marginBottom: '3mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
|
||||
{Icon && <Icon size={16} strokeWidth={1.5} />}
|
||||
</div>
|
||||
<div style={{ fontFamily: MONO, fontSize: '7pt', fontWeight: 700, color: tintDark, textTransform: 'uppercase', letterSpacing: '0.16em', marginBottom: '1mm' }}>{String(i + 1).padStart(2, '0')} · {p.kicker.replace(/^Säule[\s·]+|^Under the Hood|^Pillar[\s·]+/i, '').trim() || p.kicker}</div>
|
||||
<div style={{ fontSize: '12pt', fontWeight: 700, color: COLORS.slate900, lineHeight: 1.15, marginBottom: '2.5mm', letterSpacing: '-0.005em' }}>{p.title}</div>
|
||||
<div style={{ fontSize: '8pt', color: COLORS.slate700, lineHeight: 1.45, marginBottom: '2.5mm' }}>{p.body}</div>
|
||||
{p.bullets && <Bullets dense items={p.bullets} />}
|
||||
<div style={{ marginTop: 'auto', paddingTop: '2mm', borderTop: `1px solid ${COLORS.slate200}`, display: 'flex', alignItems: 'center', gap: '2mm' }}>
|
||||
<span style={{ width: '2mm', height: '2mm', borderRadius: '50%', background: COLORS.emerald600, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
|
||||
<span style={{ fontFamily: MONO, fontSize: '6.5pt', color: tintDark, fontWeight: 700 }}>{tickers[i]}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
/* ====================================================================== */
|
||||
/* KPI HERO — 8 trajectory tiles 2026 → 2030 */
|
||||
/* ====================================================================== */
|
||||
|
||||
function fmtEur(n: number): string {
|
||||
const abs = Math.abs(n)
|
||||
const sign = n < 0 ? '−' : ''
|
||||
if (abs >= 1e6) return `${sign}€${(abs / 1e6).toLocaleString('de-DE', { maximumFractionDigits: 1 })}M`
|
||||
if (abs >= 1e3) return `${sign}€${Math.round(abs / 1e3)}k`
|
||||
return `${sign}€${Math.round(abs)}`
|
||||
}
|
||||
|
||||
export function PrintKPIHeroPage({ fmResults, lang, pageNum, totalPages, versionName }: SlideBase & { fmResults: FMResult[] }) {
|
||||
const de = lang === 'de'
|
||||
const kpis = computeAnnualKPIs(fmResults)
|
||||
const k26 = kpis.find(k => k.year === 2026)
|
||||
const k30 = kpis.find(k => k.year === 2030)
|
||||
const breakEvenYear = kpis.find(k => k.ebit > 0)?.year
|
||||
|
||||
const tiles = k26 && k30 ? [
|
||||
{ label: 'ARR', start: fmtEur(k26.arr), end: fmtEur(k30.arr), endColor: COLORS.violet600 },
|
||||
{ label: de ? 'Kunden' : 'Customers', start: k26.customers.toLocaleString('de-DE'), end: k30.customers.toLocaleString('de-DE'), endColor: COLORS.violet600 },
|
||||
{ label: de ? 'ARPU / Mo' : 'ARPU / mo', start: fmtEur(k26.arpu), end: fmtEur(k30.arpu), endColor: COLORS.slate900 },
|
||||
{ label: de ? 'Mitarbeiter' : 'Employees', start: String(k26.employees), end: String(k30.employees), endColor: COLORS.slate900 },
|
||||
{ label: de ? 'Bruttomarge' : 'Gross margin', start: `${k26.grossMargin}%`, end: `${k30.grossMargin}%`, endColor: COLORS.emerald700 },
|
||||
{ label: 'EBIT', start: fmtEur(k26.ebit), end: fmtEur(k30.ebit), endColor: k30.ebit >= 0 ? COLORS.emerald700 : COLORS.red700 },
|
||||
{ label: de ? 'Netto-Ergebnis' : 'Net income', start: fmtEur(k26.netIncome), end: fmtEur(k30.netIncome), endColor: k30.netIncome >= 0 ? COLORS.emerald700 : COLORS.red700 },
|
||||
{ label: de ? 'Cash (Dez)' : 'Cash (Dec)', start: fmtEur(k26.cashBalance), end: fmtEur(k30.cashBalance), endColor: COLORS.emerald700 },
|
||||
] : []
|
||||
|
||||
return (
|
||||
<Page kicker="18" section={de ? 'ANHANG · KENNZAHLEN' : 'APPENDIX · KEY METRICS'} title={de ? 'Trajektorie 2026 → 2030.' : 'Trajectory 2026 → 2030.'} subtitle={de ? `Acht investorrelevante KPIs auf einen Blick. Base-Case-Szenario, abgeleitet aus dem Finanzplan. Break-Even: ${breakEvenYear ?? 'Q3 2029'}.` : `Eight investor-relevant KPIs at a glance. Base-case scenario, derived from the financial plan. Break-even: ${breakEvenYear ?? 'Q3 2029'}.`} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
|
||||
|
||||
{tiles.length === 0 ? (
|
||||
<p style={{ fontSize: '10pt', color: COLORS.slate600 }}>{de ? 'Keine Finanzdaten verfügbar.' : 'No financial data available.'}</p>
|
||||
) : (
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gridTemplateRows: '1fr 1fr', gap: '5mm', flex: 1, minHeight: 0 }}>
|
||||
{tiles.map((t, i) => (
|
||||
<div key={i} style={{ border: `1px solid ${COLORS.slate200}`, borderTop: `3px solid ${COLORS.violet600}`, background: '#ffffff', padding: '5mm', display: 'flex', flexDirection: 'column', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
|
||||
<div style={{ fontFamily: MONO, fontSize: '7.5pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.18em', marginBottom: '4mm' }}>{t.label}</div>
|
||||
<div style={{ display: 'flex', alignItems: 'baseline', gap: '3mm', marginBottom: 'auto' }}>
|
||||
<span style={{ fontSize: '11pt', fontWeight: 600, color: COLORS.slate400, fontVariantNumeric: 'tabular-nums' }}>{t.start}</span>
|
||||
<span style={{ fontFamily: MONO, fontSize: '11pt', color: COLORS.slate400, fontWeight: 700 }}>→</span>
|
||||
<span style={{ fontSize: '24pt', fontWeight: 800, color: t.endColor, lineHeight: 1, fontVariantNumeric: 'tabular-nums', letterSpacing: '-0.025em' }}>{t.end}</span>
|
||||
</div>
|
||||
<div style={{ marginTop: '3mm', paddingTop: '2mm', borderTop: `1px solid ${COLORS.slate100}`, fontFamily: MONO, fontSize: '6.5pt', color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.12em', fontWeight: 700 }}>2026 · 2030</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
/* ====================================================================== */
|
||||
/* TECH STACK — 8-category grid */
|
||||
/* ====================================================================== */
|
||||
|
||||
export function PrintTechStackPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
|
||||
const de = lang === 'de'
|
||||
|
||||
const cats = de ? [
|
||||
{ name: 'Frontend', icon: ScanLine, items: ['Next.js 15', 'React 19', 'Tailwind CSS', 'Framer Motion', 'Dioxus (Rust)'] },
|
||||
{ name: 'Backend', icon: Wrench, items: ['Go/Gin', 'Python/FastAPI', 'Rust/Axum', 'OpenAPI'] },
|
||||
{ name: 'Storage', icon: Database, items: ['PostgreSQL 16', 'MongoDB', 'Qdrant Vector DB', 'Valkey (cache)'] },
|
||||
{ name: 'KI / RAG', icon: Brain, items: ['LiteLLM', 'Qwen3-32B', 'DeepSeek-R1', 'Sentence-Transformers', 'LangGraph'] },
|
||||
{ name: 'Code-Scanning', icon: Shield, items: ['Semgrep', 'Gitleaks', 'Syft', 'Trivy', 'CycloneDX'] },
|
||||
{ name: 'Auth & SSO', icon: Lock, items: ['Keycloak', 'OIDC', 'OPA (policies)'] },
|
||||
{ name: 'Kommunikation', icon: MessageSquare, items: ['Matrix (chat)', 'Jitsi (video)', 'Mailpit'] },
|
||||
{ name: 'DevOps', icon: ShieldCheck, items: ['Gitea', 'Woodpecker CI', 'HashiCorp Vault', 'Orca', 'Docker Compose'] },
|
||||
] : [
|
||||
{ name: 'Frontend', icon: ScanLine, items: ['Next.js 15', 'React 19', 'Tailwind CSS', 'Framer Motion', 'Dioxus (Rust)'] },
|
||||
{ name: 'Backend', icon: Wrench, items: ['Go/Gin', 'Python/FastAPI', 'Rust/Axum', 'OpenAPI'] },
|
||||
{ name: 'Storage', icon: Database, items: ['PostgreSQL 16', 'MongoDB', 'Qdrant vector DB', 'Valkey (cache)'] },
|
||||
{ name: 'AI / RAG', icon: Brain, items: ['LiteLLM', 'Qwen3-32B', 'DeepSeek-R1', 'Sentence-Transformers', 'LangGraph'] },
|
||||
{ name: 'Code scanning', icon: Shield, items: ['Semgrep', 'Gitleaks', 'Syft', 'Trivy', 'CycloneDX'] },
|
||||
{ name: 'Auth & SSO', icon: Lock, items: ['Keycloak', 'OIDC', 'OPA (policies)'] },
|
||||
{ name: 'Communication', icon: MessageSquare, items: ['Matrix (chat)', 'Jitsi (video)', 'Mailpit'] },
|
||||
{ name: 'DevOps', icon: ShieldCheck, items: ['Gitea', 'Woodpecker CI', 'HashiCorp Vault', 'Orca', 'Docker Compose'] },
|
||||
]
|
||||
|
||||
return (
|
||||
<Page kicker="26" section={de ? 'ANHANG · TECH-STACK' : 'APPENDIX · TECH STACK'} title={de ? '8 Kategorien. Polyglott. 100 % Open Source.' : '8 categories. Polyglot. 100 % open source.'} subtitle={de ? 'Alle Komponenten mit kommerziell nutzbarer Lizenz (MIT, Apache-2.0, BSD, ISC, MPL-2.0, LGPL). Keine GPL/AGPL. Keine US-SaaS-Abhängigkeit.' : 'All components carry a commercially usable license (MIT, Apache-2.0, BSD, ISC, MPL-2.0, LGPL). No GPL/AGPL. No US SaaS dependency.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gridTemplateRows: '1fr 1fr', gap: '4mm', flex: 1, minHeight: 0 }}>
|
||||
{cats.map((c, i) => {
|
||||
const Icon = c.icon
|
||||
return (
|
||||
<div key={i} style={{ border: `1px solid ${COLORS.slate200}`, borderTop: `2px solid ${COLORS.violet600}`, background: '#ffffff', padding: '4mm 5mm', display: 'flex', flexDirection: 'column', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '3mm', marginBottom: '3mm' }}>
|
||||
<div style={{ width: '8mm', height: '8mm', background: COLORS.violet50, borderRadius: '2pt', display: 'flex', alignItems: 'center', justifyContent: 'center', color: COLORS.violet600, flexShrink: 0, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
|
||||
<Icon size={15} strokeWidth={1.5} />
|
||||
</div>
|
||||
<div style={{ fontSize: '11pt', fontWeight: 700, color: COLORS.slate900, lineHeight: 1.15 }}>{c.name}</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '0' }}>
|
||||
{c.items.map((it, j) => (
|
||||
<div key={j} style={{ fontFamily: MONO, fontSize: '8pt', color: COLORS.slate700, padding: '1.2mm 0', borderTop: j > 0 ? `1px solid ${COLORS.slate100}` : 'none', lineHeight: 1.3 }}>{it}</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
/* ====================================================================== */
|
||||
/* ANHANG DIVIDER (moved from PrintAnnexSlides.tsx) */
|
||||
/* ====================================================================== */
|
||||
|
||||
export function PrintAnnexDividerPage({ lang, pageNum, totalPages, versionName }: SlideBase) {
|
||||
const de = lang === 'de'
|
||||
|
||||
const sections: [string, string, string][] = de ? [
|
||||
['18', 'Go-to-Market Strategie', 'Pilot · Skalierung · Expansion'],
|
||||
['19', 'Kennzahlen 2026 → 2030', '8 KPIs Trajektorie · Base-Case'],
|
||||
['20', 'Finanzplan', 'P&L 2026–2030 · KPI-Dashboard'],
|
||||
['21', 'P&L Detail', 'Annualisierte Gewinn-/Verlust-Rechnung'],
|
||||
['22', 'Treibervariablen', 'Annahmen + Sensitivitätsszenarien'],
|
||||
['23', 'Regulatorische Details', 'DSGVO · AI Act · NIS-2 · CRA · MaschVO'],
|
||||
['24', 'Systemarchitektur', '3 Tiers · LiteLLM Gateway · lokale Inferenz'],
|
||||
['25', 'Engineering Deep Dive', '500K+ LoC · 45 Container · 100 % Self-Hosted'],
|
||||
['26', 'Tech-Stack', '8 Kategorien · polyglott · Open Source'],
|
||||
['27', 'KI-Pipeline', 'RAG · Multi-Agent · Document Intelligence · QA'],
|
||||
['28', 'Risiken & Mitigation', '10 Risiken in 5 Kategorien'],
|
||||
['29', 'Glossar', '30 Begriffe · Compliance · Engineering · Recht'],
|
||||
] : [
|
||||
['18', 'Go-to-Market Strategy', 'Pilot · Scale · Expansion'],
|
||||
['19', 'KPIs 2026 → 2030', '8 KPI trajectory · base case'],
|
||||
['20', 'Financial Plan', 'P&L 2026–2030 · KPI dashboard'],
|
||||
['21', 'P&L Detail', 'Annualized profit & loss'],
|
||||
['22', 'Driver Variables', 'Assumptions + sensitivity scenarios'],
|
||||
['23', 'Regulatory Details', 'GDPR · AI Act · NIS-2 · CRA · Machinery Reg.'],
|
||||
['24', 'System Architecture', '3 tiers · LiteLLM gateway · local inference'],
|
||||
['25', 'Engineering Deep Dive', '500K+ LoC · 45 containers · 100 % self-hosted'],
|
||||
['26', 'Tech Stack', '8 categories · polyglot · open source'],
|
||||
['27', 'AI Pipeline', 'RAG · multi-agent · document intelligence · QA'],
|
||||
['28', 'Risks & Mitigation', '10 risks across 5 categories'],
|
||||
['29', 'Glossary', '30 terms · compliance · engineering · law'],
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="print-page-break">
|
||||
<div className="print-page print-page-bg" style={{
|
||||
width: '297mm', height: '210mm', color: COLORS.slate900,
|
||||
fontFamily: "'Inter', system-ui, sans-serif", boxSizing: 'border-box',
|
||||
padding: '14mm 18mm', margin: '0 auto 24px',
|
||||
boxShadow: '0 4px 24px rgba(59,26,122,0.10)', overflow: 'hidden',
|
||||
WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact',
|
||||
display: 'flex', flexDirection: 'column',
|
||||
}}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderBottom: `1px solid ${COLORS.slate200}`, paddingBottom: '3mm' }}>
|
||||
<span style={{ fontFamily: MONO, fontSize: '7.5pt', fontWeight: 700, color: COLORS.violet600, textTransform: 'uppercase', letterSpacing: '0.22em' }}>{de ? 'Teil II · Anhang' : 'Part II · Appendix'}</span>
|
||||
<span style={{ fontFamily: MONO, fontSize: '7pt', color: COLORS.slate500, letterSpacing: '0.16em', textTransform: 'uppercase', fontWeight: 700 }}>BreakPilot · ComplAI</span>
|
||||
</div>
|
||||
|
||||
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center', maxWidth: '260mm' }}>
|
||||
<div style={{ fontFamily: MONO, fontSize: '10pt', fontWeight: 700, color: COLORS.violet600, textTransform: 'uppercase', letterSpacing: '0.3em', marginBottom: '5mm' }}>
|
||||
{de ? '17 · Kapitelwechsel' : '17 · Chapter break'}
|
||||
</div>
|
||||
<h1 style={{ fontSize: '64pt', fontWeight: 800, color: COLORS.slate900, lineHeight: 0.95, letterSpacing: '-0.035em', margin: 0 }}>
|
||||
{de ? 'Anhang' : 'Appendix'}<span style={{ color: COLORS.violet600 }}>.</span>
|
||||
</h1>
|
||||
<div style={{ height: '3px', width: '60mm', background: `linear-gradient(90deg, ${COLORS.violet700} 0%, ${COLORS.violet400} 50%, ${COLORS.violet700} 100%)`, marginTop: '5mm', marginBottom: '6mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
|
||||
<p style={{ fontSize: '13pt', fontWeight: 500, color: COLORS.slate700, lineHeight: 1.3, margin: 0, letterSpacing: '-0.008em', maxWidth: '230mm' }}>
|
||||
{de
|
||||
? 'Detailangaben & Belege. Was wir in der Pitch gesagt haben, mit Quellen, Zahlen und Architektur belegt.'
|
||||
: 'Detail & evidence. Everything we claimed in the pitch, backed by sources, numbers and architecture.'}
|
||||
</p>
|
||||
|
||||
<div style={{ marginTop: '8mm' }}>
|
||||
<div style={{ fontFamily: MONO, fontSize: '8pt', fontWeight: 700, color: COLORS.slate500, textTransform: 'uppercase', letterSpacing: '0.18em', marginBottom: '4mm' }}>
|
||||
{de ? 'Auf den folgenden Seiten' : 'On the following pages'}
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '4mm 8mm' }}>
|
||||
{sections.map(([n, t, sub]) => (
|
||||
<div key={n} style={{ display: 'flex', alignItems: 'flex-start', gap: '4mm' }}>
|
||||
<span style={{ fontFamily: MONO, fontSize: '13pt', fontWeight: 800, color: COLORS.violet600, lineHeight: 1, fontVariantNumeric: 'tabular-nums', letterSpacing: '-0.02em', minWidth: '11mm' }}>{n}</span>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ fontSize: '9.5pt', fontWeight: 700, color: COLORS.slate900, lineHeight: 1.2 }}>{t}</div>
|
||||
<div style={{ fontSize: '7pt', color: COLORS.slate600, marginTop: '0.5mm', lineHeight: 1.35 }}>{sub}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="print-mono" style={{ fontFamily: MONO, paddingTop: '3mm', borderTop: `1px solid ${COLORS.slate200}`, display: 'flex', alignItems: 'center', justifyContent: 'space-between', fontSize: '7pt', color: COLORS.slate500, letterSpacing: '0.16em', textTransform: 'uppercase', fontWeight: 700 }}>
|
||||
<span>BreakPilot · ComplAI</span>
|
||||
<span style={{ color: COLORS.violet600 }}>{versionName}</span>
|
||||
<span style={{ fontVariantNumeric: 'tabular-nums' }}>{String(pageNum).padStart(2, '0')} / {String(totalPages).padStart(2, '0')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -61,48 +61,42 @@ export function PrintUSPPage1({ lang, pageNum, totalPages, versionName }: SlideB
|
||||
)
|
||||
}
|
||||
|
||||
/* ===== USP, PAGE 2 (under the hood + closing loop) ===== */
|
||||
/* ===== USP, PAGE 2 — just the closing loop (was: 4 cards + loop) =====
|
||||
*
|
||||
* The 4 under-the-hood cards moved to the dedicated Differentiators slide
|
||||
* (PrintDifferentiatorsPage). This page is now a hero "Compliance ↔ Code
|
||||
* always in sync" closing card for the USP block.
|
||||
*/
|
||||
|
||||
export function PrintUSPPage2({ lang, pageNum, totalPages, versionName }: SlideBase) {
|
||||
const de = lang === 'de'
|
||||
const d = getDetails(de)
|
||||
const cards = ['trace', 'engine', 'opt', 'stack'] as const
|
||||
const MONO = "'JetBrains Mono', ui-monospace, monospace"
|
||||
|
||||
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}>
|
||||
<Page kicker="05" section={de ? 'USP · 2 / 2' : 'USP · 2 / 2'} title={de ? 'Compliance ↔ Code, immer in Sync.' : 'Compliance ↔ Code, always in sync.'} subtitle={de ? 'Eine geschlossene Schleife: jede Policy-Änderung fliesst in den Code; jede Code-Änderung in die Policy zurück. Zero Drift, eine Quelle der Wahrheit.' : 'A closed loop: every policy change flows into code; every code change flows back into policy. Zero drift, one source of truth.'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '5mm' }}>
|
||||
{cards.map(k => {
|
||||
const p = d[k]
|
||||
const Icon = USP_ICON[k]
|
||||
return (
|
||||
<div key={k} style={{ borderLeft: `2px solid ${COLORS.amber600}`, paddingLeft: '5mm', display: 'flex', flexDirection: 'column' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '3mm', marginBottom: '2mm' }}>
|
||||
{Icon && <Icon size={18} strokeWidth={1.5} color={COLORS.amber700} />}
|
||||
<span style={{ fontSize: '7.5pt', fontWeight: 700, color: COLORS.amber700, textTransform: 'uppercase', letterSpacing: '0.12em' }}>{p.kicker}</span>
|
||||
</div>
|
||||
<div style={{ fontSize: '12pt', fontWeight: 700, color: COLORS.slate900, lineHeight: 1.2, marginBottom: '2.5mm' }}>{p.title}</div>
|
||||
<div style={{ fontSize: '8.5pt', color: COLORS.slate700, lineHeight: 1.5, marginBottom: '2.5mm' }}>{p.body}</div>
|
||||
{p.bullets && <Bullets dense items={p.bullets} />}
|
||||
{/* Full-page hero loop diagram */}
|
||||
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
|
||||
<div style={{ border: `1px solid ${COLORS.violet300}`, background: `linear-gradient(135deg, ${COLORS.violet50} 0%, #ffffff 50%, ${COLORS.violet50} 100%)`, borderRadius: '6pt', padding: '12mm 14mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '4mm', marginBottom: '6mm' }}>
|
||||
<div style={{ width: '12mm', height: '12mm', borderRadius: '50%', background: COLORS.violet600, color: '#ffffff', display: 'flex', alignItems: 'center', justifyContent: 'center', boxShadow: `0 0 0 4px ${COLORS.violet50}`, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
|
||||
<Infinity size={22} strokeWidth={2} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Closing loop: violet-tinted hero panel with the diagram on the right */}
|
||||
<div style={{ marginTop: '6mm', border: `1px solid ${COLORS.violet300}`, background: `linear-gradient(135deg, ${COLORS.violet50} 0%, #ffffff 50%, ${COLORS.violet50} 100%)`, borderRadius: '3pt', padding: '5mm 6mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact', display: 'grid', gridTemplateColumns: '1fr 1.2fr', gap: '6mm', alignItems: 'center' }}>
|
||||
<div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '3mm', marginBottom: '3mm' }}>
|
||||
<div style={{ width: '10mm', height: '10mm', borderRadius: '50%', background: COLORS.violet600, color: '#ffffff', display: 'flex', alignItems: 'center', justifyContent: 'center', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
|
||||
<Infinity size={18} strokeWidth={2} />
|
||||
</div>
|
||||
<span style={{ fontFamily: "'JetBrains Mono', ui-monospace, monospace", fontSize: '7.5pt', fontWeight: 700, color: COLORS.violet700, textTransform: 'uppercase', letterSpacing: '0.2em' }}>{de ? 'Die Schleife · Always in Sync' : 'The Loop · Always in Sync'}</span>
|
||||
<span style={{ fontFamily: MONO, fontSize: '9pt', fontWeight: 700, color: COLORS.violet700, textTransform: 'uppercase', letterSpacing: '0.24em' }}>{de ? 'Die Schleife · Always in Sync' : 'The Loop · Always in Sync'}</span>
|
||||
</div>
|
||||
<div style={{ fontSize: '14pt', fontWeight: 800, color: COLORS.slate900, marginBottom: '2mm', lineHeight: 1.15, letterSpacing: '-0.01em' }}>{d.hub.title}</div>
|
||||
<div style={{ fontSize: '8.5pt', color: COLORS.slate700, lineHeight: 1.55 }}>{d.hub.body}</div>
|
||||
</div>
|
||||
<div style={{ background: '#ffffff', border: `1px solid ${COLORS.violet200}`, borderRadius: '3pt', padding: '3mm 4mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
|
||||
<LoopDiagram lang={lang} />
|
||||
<div style={{ fontSize: '18pt', fontWeight: 800, color: COLORS.slate900, marginBottom: '4mm', lineHeight: 1.15, letterSpacing: '-0.01em', maxWidth: '210mm' }}>{d.hub.title}</div>
|
||||
<div style={{ fontSize: '10pt', color: COLORS.slate700, lineHeight: 1.55, marginBottom: '6mm', maxWidth: '220mm' }}>{d.hub.body}</div>
|
||||
|
||||
<div style={{ background: '#ffffff', border: `1px solid ${COLORS.violet200}`, borderRadius: '3pt', padding: '5mm 6mm', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
|
||||
<LoopDiagram lang={lang} />
|
||||
</div>
|
||||
|
||||
{d.hub.bullets && (
|
||||
<div style={{ marginTop: '5mm' }}>
|
||||
<Bullets dense tone="accent" items={d.hub.bullets} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Page>
|
||||
|
||||
Reference in New Issue
Block a user