fix(pitch-deck): PDF print layout — fill page height, fix page breaks
Build pitch-deck / build-push-deploy (push) Successful in 2m2s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 44s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 33s

- Switch from inline pageBreakAfter to CSS class `.print-page` with
  explicit `page-break-after: always !important` so Chrome print
  preview creates a new page per slide (was collapsing to 2 pages)
- Remove margin/box-shadow in @media print so A4 boundaries align
- Content areas now use flex:1 so cards/pillars stretch to fill the
  full page height (no more blank void below content)
- Remove conditional rendering on data-dependent slides — always
  render all 9 core pages
- Larger font sizes throughout (11px body, 13px card titles)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-05-12 13:40:31 +02:00
parent 2e8cbfff3f
commit cf18b1074a
3 changed files with 181 additions and 221 deletions
@@ -2,25 +2,25 @@ import { PrintPage, SectionTitle, PrintTable, Badge, COLORS } from './PrintLayou
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 zu lassen, kommt für die meisten nicht in Frage. Wer auf US-KI verzichtet, verliert den Anschluss. Wer sie nutzt, riskiert seine Datensouveränität.' },
{ title: '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.' },
{ 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. KMU können die Compliance-Kosten nicht mehr allein stemmen.' },
{ 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 is out of the question for most. Those avoiding US AI fall behind. Those using it risk their data sovereignty.' },
{ 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.' },
{ title: 'Regulation Tsunami', stat: 'Unsustainable', desc: 'Since 2024, the AI Act, NIS2 and CRA apply — on top of GDPR, Data Act, Machinery Regulation. European companies bear compliance costs that US and Asian competitors do not face.' },
{ 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. 15.000+ EUR/Jahr Pentest-Kosten gespart.' },
{ title: 'Compliance auf Autopilot', desc: 'VVT, TOMs, DSFA, Löschfristen, CE-Risikobeurteilung automatisch. Nach dem Audit: Abweichungen End-to-End — Rollen, Stichtage, Tickets, Nachweise, Eskalation bis zur GF.' },
{ title: 'Deutsche Cloud, volle Integration', desc: 'BSI-zertifizierte Cloud in Deutschland. Live-Support via Jitsi und Matrix. Keine US-SaaS im Source Code. Optional Mac Mini/Studio für maximale Privacy.' },
{ 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 your issue tracker. EUR 15,000+ per year in pentest costs saved.' },
{ title: 'Compliance on Autopilot', desc: 'RoPA, TOMs, DPIA, retention policies, CE risk assessment generated automatically. Post-audit deviations end-to-end: roles, deadlines, tickets, evidence, escalation to management.' },
{ title: 'German Cloud, Full Integration', desc: 'BSI-certified cloud in Germany. Live support via Jitsi and Matrix. No US SaaS in source code. Optional Mac Mini/Studio for maximum privacy.' },
{ 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' }
@@ -31,46 +31,41 @@ export function PrintCoverPage({ company, funding, versionName, lang }: { compan
const de = lang === 'de'
const instrument = funding?.instrument || 'Pre-Seed'
return (
<div 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)', pageBreakAfter: 'always', breakAfter: 'page', overflow: 'hidden' }}>
{/* Big indigo header */}
<div style={{ height: '72px', background: 'linear-gradient(135deg, #4f46e5, #7c3aed)', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact', display: 'flex', alignItems: 'center', padding: '0 28px' }}>
<span style={{ color: '#fff', fontWeight: 800, fontSize: '28px', letterSpacing: '-0.01em' }}>BreakPilot</span>
<span style={{ color: 'rgba(255,255,255,0.55)', fontWeight: 400, fontSize: '14px', marginLeft: '12px' }}>
<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>
{/* Main content */}
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center', padding: '24px 28px 16px' }}>
<p style={{ fontSize: '11px', color: COLORS.indigo, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '8px' }}>
<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: '32px', fontWeight: 800, color: COLORS.dark, lineHeight: 1.15, margin: '0 0 10px' }}>
<h1 style={{ fontSize: '40px', fontWeight: 800, color: COLORS.dark, lineHeight: 1.1, margin: '0 0 12px' }}>
{company?.name || 'BreakPilot'}
</h1>
<p style={{ fontSize: '14px', color: COLORS.med, maxWidth: '320px', lineHeight: 1.5, margin: 0 }}>
<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>
{/* Info row */}
<div style={{ display: 'flex', gap: '32px', marginTop: '24px' }}>
{[
<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 ? 'Zielrunde' : 'Round', funding?.round_name || 'Pre-Seed'],
].map(([label, val]) => (
[de ? 'Runde' : 'Round', funding?.round_name || 'Pre-Seed'],
] as [string, string][]).map(([label, val]) => (
<div key={label}>
<p style={{ fontSize: '8px', color: COLORS.light, textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: '2px' }}>{label}</p>
<p style={{ fontSize: '12px', fontWeight: 700, color: COLORS.dark }}>{val}</p>
<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>
{/* Footer */}
<div style={{ height: '36px', backgroundColor: '#f5f3ff', borderTop: `1px solid ${COLORS.border}`, display: 'flex', alignItems: 'center', justifyContent: 'center', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<span style={{ fontSize: '9px', color: COLORS.indigo, fontWeight: 600, letterSpacing: '0.08em', textTransform: 'uppercase' }}>
<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>
@@ -86,19 +81,19 @@ export function PrintProblemPage({ lang, pageNum, totalPages, versionName }: Sli
<SectionTitle subtitle={de ? 'Europäische Unternehmen im Dilemma' : 'European companies in a dilemma'}>
{de ? 'Das Problem' : 'The Problem'}
</SectionTitle>
<div style={{ display: 'flex', gap: '10px', marginTop: '10px' }}>
<div style={{ display: 'flex', gap: '12px', flex: 1 }}>
{cards.map((c) => (
<div key={c.title} style={{ flex: 1, border: `1px solid ${COLORS.border}`, borderRadius: '8px', padding: '12px', borderTop: `3px solid ${COLORS.indigo}` }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', marginBottom: '8px' }}>
<span style={{ fontSize: '8px', fontWeight: 700, color: '#fff', background: COLORS.indigo, padding: '2px 7px', borderRadius: '99px', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>{c.stat}</span>
<span style={{ fontSize: '11px', fontWeight: 700, color: COLORS.dark }}>{c.title}</span>
<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: '9px', color: COLORS.med, lineHeight: 1.55, margin: 0 }}>{c.desc}</p>
<p style={{ fontSize: '11px', color: COLORS.med, lineHeight: 1.6, margin: 0, flex: 1 }}>{c.desc}</p>
</div>
))}
</div>
<div style={{ marginTop: '14px', padding: '10px 14px', background: '#f5f3ff', borderRadius: '8px', borderLeft: `3px solid ${COLORS.indigo}`, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<p style={{ fontSize: '9px', fontStyle: 'italic', color: COLORS.med, margin: 0, lineHeight: 1.5 }}>
<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>
@@ -115,24 +110,22 @@ export function PrintSolutionPage({ lang, pageNum, totalPages, versionName }: Sl
<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: '10px', marginTop: '10px' }}>
<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: '8px', padding: '12px' }}>
<div style={{ width: '28px', height: '28px', borderRadius: '6px', background: COLORS.indigoLight, display: 'flex', alignItems: 'center', justifyContent: 'center', marginBottom: '8px', fontSize: '14px', color: COLORS.indigo, WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }}>
<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: '11px', fontWeight: 700, color: COLORS.dark, marginBottom: '6px' }}>{p.title}</h3>
<p style={{ fontSize: '9px', color: COLORS.med, lineHeight: 1.55, margin: 0 }}>{p.desc}</p>
<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: '12px', display: 'flex', gap: '8px' }}>
<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>
))}
).map(tag => <Badge key={tag}>{tag}</Badge>)}
</div>
</PrintPage>
)
@@ -142,7 +135,7 @@ export function PrintProductPage({ products, lang, pageNum, totalPages, versionN
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: '7px', color: COLORS.indigo, fontWeight: 700 }}></span> : null}</span>,
<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(', ') || '—',
@@ -152,13 +145,15 @@ export function PrintProductPage({ products, lang, pageNum, totalPages, versionN
<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={{ marginTop: '10px' }}>
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'space-between' }}>
<PrintTable headers={headers} rows={rows} colWidths={['22%', '16%', '20%', '42%']} />
</div>
<div style={{ marginTop: '12px', padding: '8px 12px', background: '#f0fdf4', borderRadius: '6px', border: '1px solid #bbf7d0' }}>
<p style={{ fontSize: '9px', color: '#166534', margin: 0 }}>
{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 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>
)
@@ -166,15 +161,30 @@ export function PrintProductPage({ products, lang, pageNum, totalPages, versionN
export function PrintMarketPage({ market, lang, pageNum, totalPages, versionName }: SlideBase & { market: PitchMarket[] }) {
const de = lang === 'de'
const headers = [de ? 'Segment' : 'Segment', de ? 'Markt' : 'Label', de ? 'Volumen' : 'Volume', de ? 'Wachstum p.a.' : 'Growth p.a.', de ? 'Quelle' : 'Source']
const rows = market.map(m => [m.market_segment.toUpperCase(), m.label, fmtEur(m.value_eur), `${m.growth_rate_pct}%`, m.source])
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={{ marginTop: '10px' }}>
<PrintTable headers={headers} rows={rows} colWidths={['10%', '30%', '18%', '16%', '26%']} />
<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>
)
@@ -187,22 +197,22 @@ export function PrintTeamPage({ team, lang, pageNum, totalPages, 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: '10px', marginTop: '10px' }}>
<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: '8px', padding: '12px', borderLeft: `3px solid ${COLORS.indigo}` }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '6px' }}>
<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: '12px', fontWeight: 700, color: COLORS.dark, margin: 0 }}>{m.name}</p>
<p style={{ fontSize: '10px', color: COLORS.indigo, margin: '2px 0 0', fontWeight: 600 }}>{de ? m.role_de : m.role_en}</p>
<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: '9px', color: COLORS.med, lineHeight: 1.5, margin: 0, maxHeight: '52px', overflow: 'hidden' }}>
<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: '6px', display: 'flex', flexWrap: 'wrap', gap: '3px' }}>
{m.expertise.slice(0, 4).map(e => <Badge key={e} color="#6b7280">{e}</Badge>)}
<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>
@@ -217,13 +227,15 @@ export function PrintMilestonesPage({ milestones, lang, pageNum, totalPages, ver
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, 12)
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)
}).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),
de ? m.title_de : m.title_en,
<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>,
])
@@ -232,8 +244,8 @@ export function PrintMilestonesPage({ milestones, lang, pageNum, totalPages, ver
<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={{ marginTop: '10px' }}>
<PrintTable headers={[de ? 'Datum' : 'Date', de ? 'Meilenstein' : 'Milestone', de ? 'Kategorie' : 'Category', de ? 'Status' : 'Status']} rows={rows} colWidths={['14%', '46%', '22%', '18%']} />
<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>
)
@@ -245,40 +257,38 @@ export function PrintTheAskPage({ funding, lang, pageNum, totalPages, versionNam
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={de ? 'The Ask' : 'The Ask'} pageNum={pageNum} totalPages={totalPages} versionName={versionName}>
<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: '24px', marginTop: '10px', alignItems: 'flex-start' }}>
<div style={{ flexShrink: 0 }}>
<p style={{ fontSize: '36px', fontWeight: 800, color: COLORS.indigo, margin: 0, lineHeight: 1.1 }}>
{amount >= 1_000_000 ? `${(amount / 1_000_000).toFixed(1)} Mio.` : amount >= 1_000 ? `${Math.round(amount / 1_000)}k` : amount.toString()}
<span style={{ fontSize: '16px', color: COLORS.light, marginLeft: '6px' }}>EUR</span>
</p>
<div style={{ marginTop: '10px', display: 'flex', flexDirection: 'column', gap: '5px' }}>
{[
<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],
].map(([k, v]) => (
<div key={k} style={{ display: 'flex', gap: '8px' }}>
<span style={{ fontSize: '9px', color: COLORS.light, minWidth: '70px' }}>{k}</span>
<span style={{ fontSize: '9px', fontWeight: 600, color: COLORS.dark }}>{v}</span>
] 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 }}>
<p style={{ fontSize: '9px', fontWeight: 700, color: COLORS.dark, textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: '8px' }}>
<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: '5px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '2px' }}>
<span style={{ fontSize: '9px', color: COLORS.med }}>{de ? u.label_de : u.label_en}</span>
<span style={{ fontSize: '9px', fontWeight: 700, color: COLORS.indigo }}>{u.percentage}%</span>
<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: '6px', background: '#e0e7ff', borderRadius: '3px', overflow: 'hidden' }}>
<div style={{ height: '100%', width: `${u.percentage}%`, background: COLORS.indigo, borderRadius: '3px', WebkitPrintColorAdjust: 'exact', printColorAdjust: 'exact' }} />
<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>
))}
@@ -14,12 +14,10 @@ interface PrintDeckProps {
lang: Language
}
const CORE_PAGES = 9
const FINANCIAL_EXTRA = 4
export default function PrintDeck({ pitchData, versionName, fmResults, fmAssumptions, financial, lang }: PrintDeckProps) {
const isWandeldarlehen = (pitchData.funding?.instrument || '').toLowerCase() === 'wandeldarlehen'
const totalPages = financial ? CORE_PAGES + FINANCIAL_EXTRA - (isWandeldarlehen ? 1 : 0) : CORE_PAGES
const hasCapTable = financial && !isWandeldarlehen
const totalPages = financial ? (hasCapTable ? 13 : 12) : 9
const annualRows = aggregateAnnualRows(fmResults)
const de = lang === 'de'
@@ -28,11 +26,7 @@ export default function PrintDeck({ pitchData, versionName, fmResults, fmAssumpt
return () => clearTimeout(t)
}, [])
function p(n: number) {
return { lang, pageNum: n, totalPages, versionName }
}
let financialPage = CORE_PAGES + 1
function p(n: number) { return { lang, pageNum: n, totalPages, versionName } }
return (
<>
@@ -41,99 +35,79 @@ export default function PrintDeck({ pitchData, versionName, fmResults, fmAssumpt
@page { size: A4 landscape; margin: 0; }
body { margin: 0; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
.no-print { display: none !important; }
.print-page {
page-break-after: always !important;
break-after: page !important;
page-break-inside: avoid !important;
break-inside: avoid !important;
margin: 0 !important;
box-shadow: none !important;
width: 297mm !important;
height: 210mm !important;
}
.print-page-cover {
page-break-after: always !important;
break-after: page !important;
margin: 0 !important;
box-shadow: none !important;
}
.print-deck-wrapper { padding: 0 !important; }
}
@media screen {
body { background: #e5e7eb; }
body { background: #d1d5db; }
}
* { box-sizing: border-box; }
`}</style>
{/* Toolbar — hidden at print */}
{/* Toolbar — screen only */}
<div className="no-print" style={{
position: 'sticky',
top: 0,
zIndex: 100,
background: '#1e1b4b',
color: '#fff',
position: 'sticky', top: 0, zIndex: 100,
background: '#1e1b4b', color: '#fff',
padding: '10px 24px',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
fontFamily: 'system-ui, -apple-system, sans-serif',
fontSize: '13px',
boxShadow: '0 2px 8px rgba(0,0,0,0.3)',
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') : (de ? 'Standard PDF' : 'Standard PDF')}
{financial ? (de ? 'PDF + Finanzen' : 'PDF + Financial') : 'Standard PDF'}
</span>
<span style={{ fontSize: '11px', opacity: 0.5 }}>{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: '#6366f1', 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' }}
>
<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' }}>
{de ? 'Schließen' : 'Close'}
</button>
</div>
</div>
{/* Print pages */}
<div style={{ padding: '24px 0' }} className="no-print-padding">
{/* Page 1: Cover */}
<div className="print-deck-wrapper" style={{ padding: '32px 0' }}>
<PrintCoverPage company={pitchData.company} funding={pitchData.funding} versionName={versionName} lang={lang} />
{/* Page 2: Problem */}
<PrintProblemPage {...p(2)} />
{/* Page 3: Solution */}
<PrintSolutionPage {...p(3)} />
{/* Page 4: Products */}
{pitchData.products?.length > 0 && (
<PrintProductPage products={pitchData.products} {...p(4)} />
)}
{/* Page 5: Market */}
{pitchData.market?.length > 0 && (
<PrintMarketPage market={pitchData.market} {...p(5)} />
)}
{/* Page 6: Team */}
{pitchData.team?.length > 0 && (
<PrintTeamPage team={pitchData.team} {...p(6)} />
)}
{/* Page 7: Milestones */}
{pitchData.milestones?.length > 0 && (
<PrintMilestonesPage milestones={pitchData.milestones} {...p(7)} />
)}
{/* Page 8-9: The Ask (always last of core) */}
<PrintProductPage products={pitchData.products || []} {...p(4)} />
<PrintMarketPage market={pitchData.market || []} {...p(5)} />
<PrintTeamPage team={pitchData.team || []} {...p(6)} />
<PrintMilestonesPage milestones={pitchData.milestones || []} {...p(7)} />
<PrintTheAskPage funding={pitchData.funding} {...p(8)} />
{/* Financial annex */}
{/* Page 9: standard last page OR financial annex start */}
{!financial && <PrintDisclaimerPage {...p(9)} />}
{financial && (
<>
{annualRows.length > 0 && (
<PrintFinancialsPage annualRows={annualRows} {...p(financialPage++)} />
)}
{fmAssumptions.length > 0 && (
<PrintAssumptionsPage assumptions={fmAssumptions} {...p(financialPage++)} />
)}
{!isWandeldarlehen && (
<PrintCapTablePage {...p(financialPage++)} />
)}
<PrintDisclaimerPage {...p(financialPage)} />
{annualRows.length > 0
? <PrintFinancialsPage annualRows={annualRows} {...p(9)} />
: <PrintDisclaimerPage {...p(9)} />
}
<PrintAssumptionsPage assumptions={fmAssumptions} {...p(10)} />
{hasCapTable && <PrintCapTablePage {...p(11)} />}
<PrintDisclaimerPage {...p(hasCapTable ? 12 : 11)} />
</>
)}
</div>
@@ -13,91 +13,81 @@ interface PrintPageProps {
totalPages: number
versionName: string
children: React.ReactNode
noPadding?: boolean
}
export function PrintPage({ title, pageNum, totalPages, versionName, children, noPadding }: PrintPageProps) {
export function PrintPage({ title, pageNum, totalPages, versionName, children }: PrintPageProps) {
return (
<div style={{
<div className="print-page" style={{
width: '297mm',
minHeight: '210mm',
height: '210mm',
backgroundColor: '#ffffff',
color: TEXT_DARK,
position: 'relative',
overflow: 'hidden',
pageBreakAfter: 'always',
breakAfter: 'page',
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: '30px',
height: '32px',
backgroundColor: INDIGO,
WebkitPrintColorAdjust: 'exact',
printColorAdjust: 'exact',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '0 16px',
padding: '0 18px',
flexShrink: 0,
}}>
<span style={{ color: '#fff', fontWeight: 700, fontSize: '11px', letterSpacing: '0.02em' }}>
BreakPilot
</span>
<span style={{ color: 'rgba(255,255,255,0.85)', fontSize: '10px' }}>{title}</span>
<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 */}
<div style={{ flex: 1, overflow: 'hidden', padding: noPadding ? 0 : '14px 20px' }}>
{/* 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: '22px',
height: '24px',
borderTop: `1px solid ${BORDER}`,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '0 16px',
padding: '0 18px',
flexShrink: 0,
}}>
<span style={{ fontSize: '8px', color: TEXT_LIGHT }}>{versionName}</span>
<span style={{ fontSize: '8px', color: INDIGO, fontWeight: 600, letterSpacing: '0.08em', textTransform: 'uppercase' }}>
CONFIDENTIAL
</span>
<span style={{ fontSize: '8px', color: TEXT_LIGHT }}>{pageNum} / {totalPages}</span>
<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>
)
}
interface SectionTitleProps {
children: React.ReactNode
subtitle?: string
}
interface SectionTitleProps { children: React.ReactNode; subtitle?: string }
export function SectionTitle({ children, subtitle }: SectionTitleProps) {
return (
<div style={{ marginBottom: '10px' }}>
<h2 style={{
fontSize: '17px',
fontWeight: 700,
color: TEXT_DARK,
borderLeft: `3px solid ${INDIGO}`,
paddingLeft: '10px',
margin: 0,
lineHeight: 1.3,
}}>
<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: '10px', color: TEXT_LIGHT, marginTop: '4px', marginLeft: '13px' }}>
<p style={{ fontSize: '11px', color: TEXT_LIGHT, marginTop: '4px', marginLeft: '13px', marginBottom: 0 }}>
{subtitle}
</p>
)}
@@ -113,7 +103,7 @@ interface TableProps {
export function PrintTable({ headers, rows, colWidths }: TableProps) {
return (
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '9px' }}>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '10px' }}>
<thead>
<tr>
{headers.map((h, i) => (
@@ -123,8 +113,8 @@ export function PrintTable({ headers, rows, colWidths }: TableProps) {
fontWeight: 700,
textTransform: 'uppercase',
letterSpacing: '0.04em',
fontSize: '8px',
padding: '5px 8px',
fontSize: '9px',
padding: '6px 9px',
textAlign: 'left',
width: colWidths?.[i],
WebkitPrintColorAdjust: 'exact',
@@ -139,13 +129,7 @@ export function PrintTable({ headers, rows, colWidths }: TableProps) {
{rows.map((row, ri) => (
<tr key={ri} style={{ backgroundColor: ri % 2 === 0 ? '#ffffff' : '#fafafa' }}>
{row.map((cell, ci) => (
<td key={ci} style={{
padding: '5px 8px',
color: TEXT_MED,
borderBottom: `1px solid ${BORDER}`,
verticalAlign: 'top',
lineHeight: 1.45,
}}>
<td key={ci} style={{ padding: '6px 9px', color: TEXT_MED, borderBottom: `1px solid ${BORDER}`, verticalAlign: 'top', lineHeight: 1.5 }}>
{cell}
</td>
))}
@@ -158,15 +142,7 @@ export function PrintTable({ headers, rows, colWidths }: TableProps) {
export function Badge({ children, color = INDIGO }: { children: React.ReactNode; color?: string }) {
return (
<span style={{
display: 'inline-block',
padding: '1px 7px',
borderRadius: '99px',
backgroundColor: `${color}22`,
color,
fontSize: '8px',
fontWeight: 600,
}}>
<span style={{ display: 'inline-block', padding: '2px 8px', borderRadius: '99px', backgroundColor: `${color}22`, color, fontSize: '9px', fontWeight: 600 }}>
{children}
</span>
)